考虑:
char amessage[] = "now is the time";
char *pmessage = "now is the time";
我从The C Programming Language第2版中读到上述两个陈述没有做同样的事情。
我一直认为数组是一种操作指针来存储一些数据的便捷方法,但显然情况并非如此...... C中数组和指针之间的“非平凡”差异是什么?
答案 0 :(得分:142)
这是一个假设的记忆图,显示了两个声明的结果:
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
字符串文字“now is the time”存储为内存地址0x00008000的16元素char数组。该存储器可能不可写;最好假设它不是。您永远不应尝试修改字符串文字的内容。
声明
char amessage[] = "now is the time";
在内存地址0x00500000处分配一个16元素的char数组,并将字符串文字的内容复制到它。这个记忆是可写的;你可以将amessage的内容改为你心中的内容:
strcpy(amessage, "the time is now");
声明
char *pmessage = "now is the time";
在内存地址0x00500010处为char分配一个指针,并将字符串文字的地址复制到它。
由于pmessage指向字符串文字,因此不应将其用作需要修改字符串内容的函数的参数:
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指向amessage:
pmessage = amessage;
然后它可以在任何地方使用消息可以使用。
答案 1 :(得分:95)
是的,但这是一个微妙的区别。基本上,前者:
char amessage[] = "now is the time";
定义一个数组,其成员位于当前作用域的堆栈空间中,而:
char *pmessage = "now is the time";
定义一个指针,该指针位于当前作用域的堆栈空间中,但在其他位置引用内存(在此处,“现在是时间”存储在内存中的其他位置,通常是字符串表)。
另请注意,由于属于第二个定义的数据(显式指针)未存储在当前作用域的堆栈空间中,因此未指定它将存储的确切位置,并且不应进行修改。
编辑:正如Mark,GMan和Pavel所指出的,当在这些变量中的任何一个上使用address-of运算符时,也存在差异。例如,& pmessage返回char **类型的指针,或指向chars指针的指针,而& amessage返回char(*)[16]类型的指针,或指向16个字符数组的指针(就像一个char **,需要被解除引用两次作为点亮点)。
答案 2 :(得分:12)
数组包含元素。指针指向它们。
第一种是简短的说法
char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';
也就是说,它是一个包含所有字符的数组。特殊初始化为您初始化它,并自动确定它的大小。数组元素是可修改的 - 您可以覆盖其中的字符。
第二种形式是指针,只指向字符。它不直接存储字符。由于数组是字符串文字,因此无法获取指针并写入指向的位置
char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */
这个代码可能会在你的盒子上崩溃。但它可能会做任何它喜欢的事情,因为它的行为是不确定的。
答案 3 :(得分:6)
我不能有用地添加其他答案,但我会在Deep C Secrets中注意到,Peter van der Linden详细介绍了这个例子。如果你问这些问题,我想你会喜欢这本书。
P.S。您可以为pmessage
分配新值。您无法为amessage
分配新值;它是不可变的。
答案 4 :(得分:5)
如果定义了一个数组,使其大小在声明时可用,sizeof(p)/sizeof(type-of-array)
将返回数组中元素的数量。
答案 5 :(得分:4)
除了在两个不同的地方分配字符串“现在是时间”的内存外,还应该记住,数组名称充当指针值而不是指针pmessage是变量。主要区别在于指针变量可以修改为指向其他位置而数组不能。
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
答案 6 :(得分:4)
第一个表单(amessage
)定义一个变量(数组),其中包含字符串"now is the time"
的副本。
第二种形式(pmessage
)定义一个变量(一个指针),它位于与字符串"now is the time"
的任何副本不同的位置。
试试这个程序:
#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
中的字符串存在于堆栈中,而pmessage
指向的字符串位于其他位置。
最后一个printf显示字符串文字的地址。如果您的编译器执行“字符串池化”,则字符串只有一个副本“现在是时间” - 您将看到其地址与amessage
的地址不同。这是因为amessage
在初始化时会获得字符串的副本。
最后,重点是amessage
将字符串存储在自己的内存中(在此示例中为堆栈),而pmessage
指向存储在其他位置的字符串。
答案 7 :(得分:4)
指针只是一个保存内存地址的变量。请注意,您正在玩“字符串文字”,这是另一个问题。内联解释的差异:基本上:
#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;
}
答案 8 :(得分:3)
第二个在ELF的某个只读部分中分配字符串。 请尝试以下方法:
#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')。
答案 9 :(得分:3)
char指针和数组之间的区别
C99 N1256草案
字符串文字有两种不同的用途:
初始化char[]
:
char c[] = "abc";
这是更多魔术&#34;,并描述于6.7.8 / 14&#34;初始化&#34;:
字符串数组可以由字符串文字初始化,可选 用括号括起来。字符串文字的连续字符(包括 如果有空间或数组的大小未知,则终止空字符)初始化 数组的元素。
所以这只是一个捷径:
char c[] = {'a', 'b', 'c', '\0'};
与任何其他常规数组一样,c
可以修改。
其他地方:它生成一个:
所以当你写:
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&#34;字符串文字&#34;:
中有记录5在转换阶段7中,将值为零的字节或代码附加到每个多字节 由字符串文字或文字产生的字符序列。多字节字符 然后,序列用于初始化静态存储持续时间和长度的数组 足以包含序列。对于字符串文字,数组元素具有 键入char,并使用多字节字符的各个字节进行初始化 序列[...]
6如果这些数组的元素具有,则未指定这些数组是否是不同的 适当的价值观如果程序试图修改这样的数组,则行为是 未定义。
6.7.8 / 32&#34;初始化&#34;给出了一个直接的例子:
示例8:声明
char s[] = "abc", t[3] = "abc";
定义&#34; plain&#34; char数组对象
s
和t
,其元素用字符串文字初始化。此声明与
相同char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
数组的内容是可修改的。另一方面,声明
char *p = "abc";
定义
p
类型&#34;指向char&#34;并初始化它以指向一个类型为#34的对象; char&#34;长度为4,其元素用字符串文字初始化。如果尝试使用p
修改数组的内容,则行为未定义。
GCC 4.8 x86-64 ELF实施
程序:
#include <stdio.h>
int main(void) {
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将char*
存储在.rodata
部分,而不是.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
答案 10 :(得分:1)
以上答案肯定已经回答了你的问题。但我想建议你阅读丹尼斯·里奇爵士撰写的The Development of C Language中的“胚胎C”一段。
答案 11 :(得分:0)
对于这一行: char amessage [] =“现在是时间”;
编译器将评估amessage的使用作为指向包含字符“now is the time”的数组开头的指针。编译器为“now is the time”分配内存,并使用字符串“now is the time”对其进行初始化。您知道该消息的存储位置,因为消息始终指的是该消息的开头。 amessage可能没有给出新值 - 它不是变量,它是字符串的名称“现在是时间”。
这一行: char * pmessage =“现在是时间”;
声明一个变量,pmessage是初始化(给定一个初始值)字符串“now is the time”的起始地址。与消息不同,pmessage可以赋予新的价值。在这种情况下,与前一种情况一样,编译器还在存储器的其他地方存储“现在是时间”。 例如,这将导致pmessage指向“i”开始“是时间”。 pmessage = pmessage + 4;
答案 12 :(得分:-1)
这是我对数组和指针之间关键差异的总结,这是我为自己做的:
//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.
答案 13 :(得分:-2)
数组是一个const指针。您无法更新其值并使其指向其他任何位置。 虽然你可以做一个指针。