C:char指针和数组之间的区别

考虑:

char amessage[] = "now is the time"; char *pmessage = "now is the time"; 

我从“C语言程序devise语言 ”第2版上看到,上面的两个语句并没有做同样的事情。

我一直认为数组是处理指针来存储某些数据的一种方便的方式,但显然不是这种情况… C中数组和指针之间的“非平凡”差异是什么?

诚然,但这是一个微妙的差异。 从本质上讲,前者:

 char amessage[] = "now is the time"; 

定义一个数组,其成员居住在当前作用域的堆栈空间中,而:

 char *pmessage = "now is the time"; 

定义一个位于当前作用域栈空间的指针,但是引用其他地方的内存(在这个“现在是时间”存储在其他地方,通常是一个string表)。

另外请注意,因为属于第二个定义(显式指针)的数据没有存储在当前作用域的堆栈空间中,所以它的存储位置并不确切,不应该被修改。

编辑:正如Mark,GMan和Pavel所指出的那样,当这两个variables之一使用操作符的地址时也是有区别的。 例如,&pmessage返回一个types为char **的指针,或者一个指向chars的指针,而&amessage返回一个types为char(*)[16]的指针,或者一个指向16个charstypes的指针一个char **需要被解除引用两次,像litb指出的那样)。

这是一个假设的内存映射,显示了两个声明的结果:

  0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x00008000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00008008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' ... amessage: 0x00500000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00500008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' pmessage: 0x00500010: 0x00 0x00 0x80 0x00 

string文字“现在是时间”被存储为内存地址为0x00008000的16元素字符数组。 该内存可能不可写; 最好假设它不是。 你不应该试图修改string文字的内容。

声明

 char amessage[] = "now is the time"; 

在内存地址0x00500000处分配一个16元素的char数组,并将string文本的内容复制到它。 这个内存是可写的; 你可以将消息的内容更改为你心中的内容:

 strcpy(amessage, "the time is now"); 

声明

 char *pmessage = "now is the time"; 

分配一个指向内存地址为0x00500010的char的单个指针,并将string的地址复制到它。

由于pmessage指向string文字,因此不应将其用作需要修改string内容的函数的参数:

 strcpy(amessage, pmessage); /* OKAY */ strcpy(pmessage, amessage); /* NOT OKAY */ strtok(amessage, " "); /* OKAY */ strtok(pmessage, " "); /* NOT OKAY */ scanf("%15s", amessage); /* OKAY */ scanf("%15s", pmessage); /* NOT OKAY */ 

等等。 如果您将pmessage更改为指向消息:

 pmessage = amessage; 

那么它可以用在任何地方,可以使用消息。

一个数组包含元素。 指针指向他们。

首先是简短的说法

 char amessage[16]; amessage[0] = 'n'; amessage[1] = 'o'; ... amessage[15] = '\0'; 

也就是说,它是一个包含所有字符的数组。 特殊的初始化为你初始化,并自动确定它的大小。 数组元素是可修改的 – 您可以覆盖它中的字符。

第二种forms是一个指针,只是指向字符。 它不直接存储字符。 由于数组是一个string,所以你不能把指针写到指向的地方

 char *pmessage = "now is the time"; *pmessage = 'p'; /* undefined behavior! */ 

这段代码可能会在你的盒子上崩溃。 但它可以做任何喜欢的事情,因为它的行为是不确定的。

我无法对其他答案进行有益的补充,但是我要指出,在Deep C的秘密中 ,Peter van der Linden详细地介绍了这个例子。 如果你问这些问题,我想你会喜欢这本书。


PS您可以给pmessage分配一个新的值。 你不能给消息分配一个新的值。 它是不可改变的

如果定义了一个数组以使其大小在声明时可用,则sizeof(p)/sizeof(type-of-array)将返回sizeof(p)/sizeof(type-of-array)的元素数。

除了string“现在是时间”被分配在两个不同的地方的内存之外,还应该记住,数组名称充当指针 ,而不是指向pmessage的指针variables 。 主要的区别是指针variables可以修改指向别的地方,数组不能。

 char arr[] = "now is the time"; char *pchar = "later is the time"; char arr2[] = "Another String"; pchar = arr2; //Ok, pchar now points at "Another String" arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE //not a pointer VARIABLE 

指针只是一个保存内存地址的variables。 请注意,您正在玩​​“string文字”这是另一个问题。 内联解释差异:基本上:

 #include <stdio.h> int main () { char amessage[] = "now is the time"; /* Attention you have created a "string literal" */ char *pmessage = "now is the time"; /* You are REUSING the string literal */ /* About arrays and pointers */ pmessage = NULL; /* All right */ amessage = NULL; /* Compilation ERROR!! */ printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/ printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/ printf ("%p, %p\n", pmessage, &pmessage); /* These values are different !! */ printf ("%p, %p\n", amessage, &amessage); /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */ /* About string literals */ if (pmessage == amessage) { printf ("A string literal is defined only once. You are sharing space"); /* Demostration */ "now is the time"[0] = 'W'; printf ("You have modified both!! %s == %s \n", amessage, pmessage); } /* Hope it was useful*/ return 0; } 

第一种forms( amessage )定义了一个variables(一个数组),其中包含string"now is the time"的副本。

第二种forms( pmessage )定义了一个variables(一个指针),它位于与"now is the time"string的任何副本不同的位置。

试试这个程序:

 #include <inttypes.h> #include <stdio.h> int main (int argc, char *argv []) { char amessage [] = "now is the time"; char *pmessage = "now is the time"; printf("&amessage : %#016"PRIxPTR"\n", (uintptr_t)&amessage); printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]); printf("&pmessage : %#016"PRIxPTR"\n", (uintptr_t)&pmessage); printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]); printf("&\"now is the time\": %#016"PRIxPTR"\n", (uintptr_t)&"now is the time"); return 0; } 

您将看到,虽然&amessage等于&amessage[0] ,但对于&pmessage&pmessage[0] ,这不是正确的。 事实上,你会发现存储在amessage中的string存在于堆栈中,而由pmessage指向的stringpmessage存在于其他地方。

最后一个printf显示了string文字的地址。 如果你的编译器做了“string池”,那么只有一个string“now is the time”的副本 – 你会看到它的地址和消息的地址不一样。 这是因为amessage在初始化时会得到一个string的副本

最后,重点在于, amessage将string存储在自己的内存中(在本例中为堆栈),而pmessage指向存储在别处的string。

第二个在ELF的一些只读部分分配string。 尝试以下操作:

 #include <stdio.h> int main(char argc, char** argv) { char amessage[] = "now is the time"; char *pmessage = "now is the time"; amessage[3] = 'S'; printf("%s\n",amessage); pmessage[3] = 'S'; printf("%s\n",pmessage); } 

你会得到第二个分配的段错误(pmessage [3] ='S')。

字符指针和数组之间的区别

C99 N1256草案

数组文字有两种完全不同的用法:

  1. 初始化char[]

     char c[] = "abc"; 

    这更“神奇”,并在6.7.8 / 14“初始化”中描述

    字符types的数组可以由string文字初始化,可选地用大括号括起来。 string文字的连续字符(包括终止空字符,如果有空间或数组未知大小)初始化数组的元素。

    所以这只是一个捷径:

     char c[] = {'a', 'b', 'c', '\0'}; 

    像任何其他常规数组一样, c可以被修改。

  2. 其他地方:它会产生:

    • 无名
    • char数组什么是C和C ++中的string文字的types?
    • 与静态存储
    • 如果修改则给予UB

    所以当你写:

     char *c = "abc"; 

    这类似于:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    注意从char[]char *的隐式转换,这总是合法的。

    那么如果你修改c[0] ,你还要修改__unnamed ,这是UB。

    这在6.4.5“string文字”中有logging

    5在翻译阶段7,将string或文字产生的每个多字节字符序列附加一个字节或值为零的代码。 然后使用多字节字符序列来初始化静态存储持续时间和长度的数组,以便足以包含该序列。 对于string文字,数组元素的types为char,并且用多字节字符序列的单个字节进行初始化[…]

    6这些数组是否是不同的,只要它们的元素具有适当的值。 如果程序试图修改这样一个数组,行为是不确定的。

6.7.8 / 32“初始化”给出了一个直接的例子:

例8:声明

 char s[] = "abc", t[3] = "abc"; 

定义“简单的”字符数组对象st其元素用string文字初始化。

这个声明是一样的

 char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; 

数组的内容是可修改的。 另一方面,声明

 char *p = "abc"; 

定义p的types为“指向char的指针”并将其初始化为指向长度为4的types为“char的数组”的对象,其元素用string文字初始化。 如果尝试使用p来修改数组的内容,则行为是不确定的。

GCC 4.8 x86-64 ELF实现

程序:

 #include <stdio.h> int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

编译和反编译:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

输出包含:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

结论:GCC在.rodata节中存储char*而不是.text

如果我们对char[]做同样的处理:

  char s[] = "abc"; 

我们获得:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

所以它被存储在堆栈中(相对于%rbp )。

但是,请注意,默认链接描述文件将.rodata.text放在同一个段中,该段执行但没有写入权限。 这可以观察到:

 readelf -l a.out 

其中包含:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 

以上答案必须回答你的问题。 但是我想build议你阅读Dennis Ritchie爵士撰写的C语言开发中的 “Embryonic C”一段。

对于这一行:char amessage [] =“现在是时间”;

编译器将评估amessage的使用作为一个指向数组的开始的持有字符“现在是时间”的指针。 编译器为“now is the time”分配内存,并用string“now is the time”初始化它。 你知道消息的存储位置,因为消息总是指消息的开始。 amessage可能不会被赋予一个新的值 – 它不是一个variables,它是string“now is the time”的名字。

这行:char * pmessage =“现在是时间”;

声明一个variables,pmessage被初始化 (给定一个初始值)string的起始地址“now is the time”。 与消息不同,消息可以被赋予新的价值。 在这种情况下,与前一种情况一样,编译器还在存储器的其他地方存储“现在是时间”。 例如,这将导致pmessage指向开始“是时间”的“我”。 pmessage = pmessage + 4;

这里是我对自己做的数组和指针之间主要区别的总结:

 //ATTENTION: //Pointer depth 1 int marr[] = {1,13,25,37,45,56}; // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT int* pmarr = marr; // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement. int* point = (marr + 1); // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int)) //Pointer depth 2 int** ppmarr = &pmarr; // use & because going one level deeper. So use the address of the pointer. //TYPES //array and pointer are different, which can be seen by checking their types std::cout << "type of marr is: " << typeid(marr).name() << std::endl; // int* so marr gives a pointer to the first array element std::cout << "type of &marr is: " << typeid(&marr).name() << std::endl; // int (*)[6] so &marr gives a pointer to the whole array std::cout << "type of pmarr is: " << typeid(pmarr).name() << std::endl; // int* so pmarr gives a pointer to the first array element std::cout << "type of &pmarr is: " << typeid(&pmarr).name() << std::endl; // int** so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper. 

一个数组是一个const指针。 你不能更新它的价值,并把它指向其他任何地方。 虽然你可以做一个指针。