C字符串:简单问题
我在下面初始化了三个变量:
char c1[] = "Hello"; char c2[] = { 'H', 'e', 'l', 'l', 'o', '\0'}; char* c3 = "Hello";
我知道c1和c2是相同的,并且它们都是字符串,因为它们被\ 0终止。 但是,c3与c1和c2不同。 这是因为c3不以\ 0结尾吗? 这是否意味着c3不是字符串? 如果c3不是字符串,那么为什么printf("%s", c3);
不给出错误? 谢谢!
编辑:
有没有理由可以修改c1和c2,但c3不能?
就C而言, c3
与其他之间最相关的区别是你不能尝试用c3
修改底层字符。 我经常觉得这样想是有帮助的:
char *xyz = "xyz";
将在堆栈上创建一个可修改的指针 ,并使其指向不可修改的字符序列{'x','y','z','\0'}
。 另一方面,
char xyz[] = "xyz";
将在堆栈上创建一个可修改的数组 ,其大小足以容纳字符序列{'x','y','z','\0'}
,然后将该字符序列复制到其中。 然后可以修改数组内容。 请记住,标准没有说明堆栈,但这通常是如何完成的。 毕竟,它只是一种记忆辅助工具。
forms上, c3
是指向字符串文字的指针,而c1
和c2
都是字符数组,两者都恰好以空字符结尾。 当它们被传递给像printf
这样的printf
,它们会衰减到指向数组的第一个元素的指针,这意味着它们将在该函数中被视为与c3
相同(实际上它们在很多情况下都会衰减,请参阅c99的第三个引用以下为例外情况)。
C99的相关部分是6.4.5 String literals
,这解释了为什么不允许修改c3
指向的内容:
如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的。 如果程序试图修改此类数组,则行为未定义。
以及为什么它有一个null终止符:
在转换阶段7中,将值0的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列。
6.3.2.1 Lvalues, arrays, and function designators
6.3 Conversions
下的6.3.2.1 Lvalues, arrays, and function designators
指出:
除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串文字,否则将类型为”array of type”的表达式转换为类型为”指针的表达式type”指向数组对象的初始元素,而不是左值。 如果数组对象具有寄存器存储类,则行为未定义。
第一点,
char* c3 = "Hello"; // may be valid C, but bad C++!
是一种容易出错的风格,所以不要使用它。 而是使用,
const char* c3 = "Hello";
这是一个有效的代码。 指针c3
指向存储"Hello"
字符串的位置的地址。 但是你不能像以前那样修改 *c3
(即*c3
内容)(如果你这样做,那就是一个未定义的行为)。
c3
是一个指向字符串的指针,这是printf("%s", ...)
期望的参数。
printf("%s", c1)
或printf("%s", c2)
也可以工作的原因是因为在C数组中,表达式中的指针非常容易“衰减”。 实际上,关于数组名称不会衰减到表达式中的指针的唯一情况是,它被用作sizeof
运算符的操作数或&
(address-of)运算符的操作数。
这导致了常见的混淆,即指针和数组在C中是等价的,这是不正确的。 只是在C数组中几乎可以在任何地方使用指针。 一个例外是它们不能被分配,除非它们是下标的(结果是一个将它们视为指针的表达式)。
请注意,最后一个字符串还有另外一个区别 – 因为它是一个sting文字,它不能被修改(如果你尝试将会发生什么,这是未定义的)。 `
c1
和c2
分配6个字节的内存并在其中存储以null结尾的字符串。
但是, c3
在程序存储器中分配(也是以null结尾的)字符串,并创建一个指向它的指针,即字符串与其他指令一起存储而不是堆栈(或堆?有人纠正我)所以编辑它会不安全。
在C中,常量"string"
可以有两个含义,基于它使用的上下文。 它可以表示可执行文件的ro
部分中的字符串(虽然我不认为标准法术出来了),使const char *foo = "bar"
语句初始化foo
指向加载的可执行文件内存中的一个位置。 如果二进制blob( "bar"
)确实在ro
部分中,并且你执行类似foo[0] = 'x'
,那么最终会得到一个SIGSEGV
。
但是,当你写char x[] = "Hello"
(或char x[6] = "Hello"
)时,你使用"Hello"
作为数组初始值设定项(如int x[2] = { 1, 2 }
), x
只是在堆栈上分配的常规(可写)数组。 在这种情况下, "Hello"
只是{'H', 'e', 'l', 'l', 'o', '\0' }
的简写。
"bar"
和"Hello"
均为空终止。
c3
不会被NUL或NULL终止。 它是指向由NUL终止的字符串的指针。
它是指向具有不同终止的字符串的指针。
它是一个字符串它指向一个字符串,但它有风险。
C3是指向字符串处第一个单元格的指针。 C1,C2只是一个没有人指出的常规数组。