char amessage[] = "now is the time";
char *pmessage = "now is the time";
我从C程序设计语言,第二版中读到,上述两个语句的作用不同。
我一直认为数组是一个方便的方式来操作指针以存储一些数据,但这显然不是这种情况...在C语言中,数组和指针之间有哪些“非平凡”的区别?
char amessage[] = "now is the time";
char *pmessage = "now is the time";
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
char amessage[] = "now is the time";
strcpy(amessage, "the time is now");
The declaration
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;
sizeof message
或 &pmessage
使用,其行为是不同的。 - M.Mpmessage
设置为指向另一个字符串将绝对不会影响存储在0x0000800
处的字符串字面量的内容。该字符串字面量的存储空间直到程序退出才会被释放。 - John Bodeamessage
这样的数组 不是 指针。数组 对象 不会在任何地方存储地址(从我的答案中的内存映射应该很清楚)。相反,除非它们是一元运算符 &
或 sizeof
的操作数,否则数组 表达式 将“衰减”为指针类型(因此 sizeof
的行为差异)。 - John Bodechar amessage[] = "now is the time";
char *pmessage = "now is the time";
&pmessage
的类型是char **
- 指向指针的指针,而&amessage
的类型是char (*) [16]
- 指向16个字符数组的指针。这两种类型不兼容(特别是第二种类型仅仅是字符串中第一个字符的地址,而第一种类型是存储第一个字符地址的变量的地址)。 - Pavel Minaev一个数组包含元素。指针指向这些元素。
前者是说的一种简略形式。
char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';
也就是说,它是一个包含所有字符的数组。特殊的初始化会为你初始化它,并自动确定其大小。数组元素是可修改的-您可以覆盖其中的字符。
第二种形式是指针,只是指向这些字符。它不直接存储字符。由于数组是字符串字面值,因此您不能获取指针并写入指针所指向的位置。
char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */
这段代码可能会在你的电脑上崩溃。但它可以做任何它想做的事情,因为它的行为是未定义的。
我对其他答案没有有益的补充,但要说明的是,在《深入C语言秘籍》一书中,Peter van der Linden详细介绍了这个例子。如果你正在问这些问题,我认为你会喜欢这本书。
附注:你可以给pmessage
赋新值。你不能给amessage
赋新值;它是不可变的。
sizeof(p)/sizeof(type-of-array)
将返回数组中元素的数量。该公式常用于计算数组的长度。初始化 char[]
:
char c[] = "abc";
这是“更多的魔法”,并在6.7.8/14“初始化”中描述:
字符类型的数组可以通过字符字符串文字进行初始化,可选地包含在大括号中。 字符串文字的连续字符(包括终止空字符,如果有空间或数组大小未知)初始化数组的元素。
所以这只是一个快捷方式:
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“字符串文字”中有记录:
5 在第7个翻译阶段,将值为零的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列。 然后使用多字节字符序列初始化具有静态存储期和长度的数组,该数组刚好足以包含该序列。 对于字符字符串文字,数组元素具有char类型,并使用多字节字符序列的各个字节进行初始化[...]
6 未指定这些数组是否不同,只要它们的元素具有适当的值即可。 如果程序尝试修改此类数组,则行为是未定义的。
6.7.8/32 "Initialization" 给出了一个直接的例子:
EXAMPLE 8: The declaration
char s[] = "abc", t[3] = "abc";
defines "plain" char array objects
s
andt
whose elements are initialized with character string literals.This declaration is identical to
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
The contents of the arrays are modifiable. On the other hand, the declaration
char *p = "abc";
defines
p
with type "pointer to char" and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to usep
to modify the contents of the array, the behavior is undefined.
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
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
指针只是一个保存内存地址的变量。请注意,您在操作“字符串字面值”,这是另一个问题。差异在下面进行了解释:基本上:
#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;
}
除了字符串“现在是时候”的内存被分配到两个不同的位置之外,您还应该记住数组名称作为指针值而不是指针变量。主要区别在于指针变量可以修改为指向其他地方,而数组则不能。
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
第一种形式(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
指向的字符串存在于其他地方。amessage
的地址不同。这是因为amessage
在初始化时获得字符串的副本。amessage
将字符串存储在自己的内存中(在此示例中为堆栈),而pmessage
指向存储在其他位置的字符串。&amessage
和 &amessage[0]
有什么区别? - Suraj Jainamessage
不是字符数组的第一个元素的指针吗?这意味着 amessage
指向 amessage[0]
,所以 &amessage[0] = amessage
,但是 &amessage
(amessage
的地址)怎么会和 amessage[0]
相同呢? - Suraj Jain#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',则会导致段错误。