以下代码在第2行出现段错误:
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
虽然这个方法完全可以正常工作:
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
已使用 MSVC 和 GCC 进行测试。
以下代码在第2行出现段错误:
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
虽然这个方法完全可以正常工作:
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
已使用 MSVC 和 GCC 进行测试。
参见 C FAQ 中的 问题 1.32
问: 这些初始化方式有什么区别?
char a[] = "字符串常量";
char *p = "字符串常量";
如果我尝试给p[i]
赋新值,我的程序会崩溃。答: 字符串常量(在 C 源代码中表示为双引号括起来的字符串)可以以两种稍微不同的方式使用:
- 作为 char 数组的初始化值,像
char a[]
的声明中一样,它指定了该数组中字符的初始值(如果需要,则还包括其大小)。- 在其他任何地方,它会变成一个未命名的静态字符数组,并且这个未命名的数组可能存储在只读内存中,因此不能保证能够修改。在表达式上下文中,该数组立即被转换为指针(如第6节所述),因此第二个声明将 p 初始化为指向未命名数组的第一个元素。
一些编译器具有控制字符串常量是否可写的开关(用于编译旧代码),而一些编译器可能具有选项,使字符串常量被正式视为 const char 数组(以更好地进行错误检测)。
一般情况下,当程序运行时,字符串常量会被存储在只读内存中。这是为了防止您意外更改字符串常量。在您的第一个示例中,"string"
存储在只读内存中,*str
指向第一个字符。当您尝试将第一个字符更改为 'z'
时,会导致段错误。
在第二个示例中,编译器将字符串 "string"
从只读内存中复制到 str[]
数组中。然后,更改第一个字符是允许的。您可以通过打印每个变量的地址来检查这一点:
printf("%p", str);
此外,在第二个示例中打印str
的大小将向您显示编译器已为其分配7个字节:
printf("%d", sizeof(str));
%zu
打印size_t
。 - phuclv这些答案大多数是正确的,但为了更加明确,需要补充一点内容...
人们所指的"只读存储器"是ASM术语中的文本段。它是内存中加载指令的同一位置。由于安全原因,这是只读的。当您创建一个char*并将其初始化为一个字符串时,该字符串数据被编译到文本段中,并且程序将指针初始化为指向文本段。因此,如果您尝试更改它,会导致segmentation fault。
如果写成数组形式,编译器会将初始化的字符串数据放在数据段中,这与全局变量等存储的位置相同。由于数据段中没有指令,因此该内存是可变的。这次编译器初始化字符数组(仍然只是一个char*),它指向数据段而不是文本段,在运行时可以安全地改变它。
初始化 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 未指定这些数组是否不同,只要它们的元素具有适当的值即可。 如果程序尝试修改此类数组,则行为是未定义的。
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
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
str"是在栈上分配的数组,可以自由修改。
"str
是全局或静态的,则在堆栈或数据段上。 - Gauthier因为第一个例子中的"whatever"
类型在上下文中是const char *
(即使你将其分配给非const char*),这意味着你不应该尝试写入它。
编译器通过将字符串放置在只读内存部分来强制执行此操作,因此对其进行写入会生成段错误。
char *str = "string";
str
指向了程序二进制图像中硬编码的字面量值"string"
,这在内存中可能被标记为只读。因此,str[0]=
试图写入应用程序的只读代码。我猜这可能与编译器有关。为了理解这个错误或问题,您首先需要了解指针和数组之间的区别,因此我首先要解释它们之间的差异。
char strarray[] = "hello";
内存数组存储在连续的内存单元中,以 [h][e][l][l][o][\0] =>[]
的形式存储,其中每个字符占用 1 字节大小的内存单元,并且可通过名称为 strarray 的名字访问这些连续的内存单元。因此,在这里字符串数组 strarray 包含初始化为其的所有字符。在本例中是 "hello"
。因此,我们可以通过访问每个字符的索引值来轻松更改其内存内容。
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
它的值改变为'm'
,因此strarray的值改变为"mello"
;
这里需要注意的一点是,我们可以逐个字符地更改字符串数组的内容,但不能直接将其他字符串初始化为它,如strarray="new string"
是无效的。
众所周知,指针指向内存中的内存位置,未初始化的指针指向随机的内存位置,初始化后指向特定的内存位置。
char *ptr = "hello";
这里指针ptr被初始化为字符串"hello"
,这是存储在只读存储器(ROM)中的常量字符串,因此"hello"
无法更改,因为它存储在ROM中。
ptr存储在堆栈段中,并指向常量字符串"hello"
因此,尝试将ptr[0]赋值为'm'是无效的,因为您无法访问只读存储器
但是,可以直接将ptr初始化为其他字符串值,因为它只是指针,因此可以指向其数据类型的任何变量内存地址。
ptr="new string"; is valid
char *str = "string";
分配一个指向字符串字面值的指针,编译器将其放置在您的可执行文件中不可修改的部分;
char str[] = "string";
分配并初始化一个可修改的本地数组
char *s = "HelloWorld"
一样写int *b = {1,2,3}
? - Suraj Jainchar* ptr = {'a','b'};
,那也将是无效的转换(从char到char)。 - Jitu DeRapschar *s = "HelloWorld"
一样写int *b = {1,2,3}
? - Suraj Jain