char数组与char中的C指针
我想了解C中的指针,但我目前与以下内容混淆:
-
char *p = "hello"
这是一个字符指针,指向字符数组,从h开始。
-
char p[] = "hello"
这是一个存储hello的数组。
将这两个variables传递给这个函数有什么区别?
void printSomething(char *p) { printf("p: %s",p); }
我不明白他们有什么不同。
char*
和char[]
是不同的types ,但在所有情况下都不是立即可见的。 这是因为数组衰减成指针 ,这意味着如果提供了char*
types的expression式,那么在期望char*
types之一时,编译器会自动将该数组转换为指向其第一个元素的指针。
你的示例函数printSomething
需要一个指针,所以如果你试图像这样传递一个数组:
char s[10] = "hello"; printSomething(s);
编译器假装你写这个:
char s[10] = "hello"; printSomething(&s[0]);
让我们来看看:
#include <stdio.h> #include <string.h> int main() { char *p = "hello"; char q[] = "hello"; // no need to count this printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64 printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both // size_t strlen(const char *s) and we don't get any warnings here: printf("%zu\n", strlen(p)); // => 5 printf("%zu\n", strlen(q)); // => 5 return 0; }
foo *和foo []是不同的types,编译器对它们的处理方式不同(指针=地址+表示指针的types,数组=指针+数组的可选长度,如果已知,例如,数组是静态分配的),细节可以在标准中find。 而在运行级别,它们之间没有任何区别(在汇编程序中,差不多,见下文)。
另外,在C FAQ中还有一个相关的问题 :
问 :这些初始化有什么区别?
char a[] = "string literal"; char *p = "string literal";
如果我尝试为p [i]分配一个新的值,我的程序崩溃。
答 :string文字(C源中双引号string的forms术语)可以用两种稍微不同的方式使用:
- 作为char数组的初始化方法,如char a []的声明中那样,它指定了该数组中字符的初始值(必要时还指定其大小)。
- 其他任何地方,它变成一个无名的,静态的字符数组,这个未命名的数组可以存储在只读存储器中,因此不一定会被修改。 在一个expression式上下文中,数组一次被转换为一个指针(见第6节),所以第二个声明将p初始化为指向未命名数组的第一个元素。
有些编译器有一个控制string文字是否可写(用于编译旧代码)的开关,有些编译器可能会有选项使string文字在forms上被当作常量字符数组(为了更好地捕获错误)。
另见问题1.31,6.1,6.2,6.8和11.8b。
参考文献:K&R2 Sec。 5.5 p。 104
ISO Sec。 6.1.4, 6.5.7
基本原理 3.1.4
H&S Sec。 2.7.4第31-2页
我不明白他们有什么不同。
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
你不能改变string常量的内容,这是第一个p
指向的内容。 第二个p
是一个用string常量初始化的数组,你可以改变它的内容。
对于这样的情况,效果是一样的:你最终将第一个字符的地址传递给一个string。
声明显然不尽相同。
以下为string和字符指针设置内存,然后初始化指针指向string中的第一个字符。
char *p = "hello";
虽然以下内容仅用于string。 所以它实际上可以使用更less的内存。
char p[10] = "hello";
据我所知,一个数组实际上是一组指针。 例如
p[1]== *(&p+1)
是一个真实的陈述
char p[3] = "hello"
? 应该是char p[6] = "hello"
记得在C中“string”的末尾有一个'\ 0'字符
无论如何,C中的数组只是一个指向内存中调整对象的第一个对象的指针。 唯一不同的是语义。 同时你可以改变指针的值来指向内存中一个不同位置的数组,创build后总会指向同一个位置。
也使用数组时,“新”和“删除”是自动完成的。