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草案
数组文字有两种完全不同的用法:
-
初始化
char[]
:char c[] = "abc";
这更“神奇”,并在6.7.8 / 14“初始化”中描述 :
字符types的数组可以由string文字初始化,可选地用大括号括起来。 string文字的连续字符(包括终止空字符,如果有空间或数组未知大小)初始化数组的元素。
所以这只是一个捷径:
char c[] = {'a', 'b', 'c', '\0'};
像任何其他常规数组一样,
c
可以被修改。 -
其他地方:它会产生:
- 无名
- 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";
定义“简单的”字符数组对象
s
和t
其元素用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指针。 你不能更新它的价值,并把它指向其他任何地方。 虽然你可以做一个指针。