为什么在写入用“char * s”初始化而不是“char s ”的string时会出现分段错误?
以下代码在第2行上收到seg故障:
char *str = "string"; str[0] = 'z'; printf("%s", str);
虽然这很好地工作:
char str[] = "string"; str[0] = 'z'; printf("%s", str);
经MSVC和GCCtesting。
参见C FAQ, 问题1.32
问 :这些初始化有什么区别?
char a[] = "string literal";
char *p = "string literal";
如果我尝试为p[i]
分配一个新的值,我的程序崩溃。答 :string文字(C源中双引号string的forms术语)可以用两种稍微不同的方式使用:
- 作为char数组的初始化方法,如
char a[]
的声明中那样,它指定了该数组中字符的初始值(必要时还指定其大小)。- 其他任何地方,它变成一个无名的,静态的字符数组,这个未命名的数组可以存储在只读存储器中,因此不一定会被修改。 在一个expression式上下文中,数组一次被转换为一个指针(见第6节),所以第二个声明将p初始化为指向未命名数组的第一个元素。
有些编译器有一个控制string文字是否可写(用于编译旧代码)的开关,有些编译器可能会有选项使string文字在forms上被当作常量字符数组(为了更好地捕获错误)。
正常情况下,程序运行时,string文字存储在只读存储器中。 这是为了防止您意外更改string常量。 在第一个例子中, "string"
存储在只读存储器中, *str
指向第一个字符。 当您尝试将第一个字符更改为'z'
。
在第二个例子中,string"string"
被编译器从它的只读home 复制到str[]
数组中。 然后改变第一个字符是允许的。 您可以通过打印每个地址来检查:
printf("%p", str);
另外,在第二个例子中打印str
的大小将会告诉你编译器已经为它分配了7个字节:
printf("%d", sizeof(str));
这些答案大部分是正确的,但只是为了增加一点清晰度…
人们所指的“只读存储器”是ASM术语中的文本段。 这是在加载指令的内存中的相同的地方。 这是只读的,因为安全性等显而易见的原因。 当你创build一个char *初始化为一个string时,string数据被编译到文本段中,程序初始化指针指向文本段。 所以,如果你试图改变它,kaboom。 段错误。
当编写为数组时,编译器会将初始化的string数据放置在数据段中,而这与全局variables等位置相同。 这个内存是可变的,因为数据段中没有指令。 这次编译器初始化字符数组(它仍然是一个char *),它指向数据段而不是文本段,在运行时可以安全地修改它。
在第一个代码中,“string”是一个string常量,string常量不应该被修改,因为它们通常被放置在只读存储器中。 “str”是一个用来修改常量的指针。
在第二个代码中,“string”是一个数组初始值设定项,简写为short
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
“str”是一个在堆栈上分配的数组,可以自由修改。
为什么在写入string时出现分段错误?
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 Linux实现
让我们看看为什么这个实现段错误。
程序:
#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
所以string存储在.rodata
节中。
然后:
readelf -l a.out
包含(简体):
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000704 0x0000000000000704 RE 200000 Section to Segment mapping: Segment Sections... 02 .text .rodata
这意味着默认链接脚本将.text
和.rodata
转储到可以执行但不能修改的段( Flags = RE
)。 尝试修改这样的段会导致Linux中发生段错误。
如果我们对char[]
做同样的处理:
char s[] = "abc";
我们获得:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
所以它被存储在堆栈中(相对于%rbp
),我们当然可以修改它。
因为在第一个例子的上下文中"whatever"
的types是const char *
(即使你把它分配给一个非const char *),这意味着你不应该尝试写入它。
编译器通过将string放在内存的只读部分来强制执行此操作,因此写入它会生成段错误。
要了解这个错误或问题,你应该先知道指针和数组的差异,所以这里先解释一下你们之间的差异
string数组
char strarray[] = "hello";
在存储器arrays中存储连续的存储单元,存储为[h][e][l][l][o][\0] =>[]
是1个char字节大小的存储单元,在这里命名为strarray here.so这里的string数组strarray
本身包含所有初始化为string的字符。在这里这里是"hello"
所以我们可以通过访问每个字符的索引值
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
它的值变成了'm'
所以价值变成了"mello"
。
需要注意的一点是,我们可以通过改变字符来改变string数组的内容,但不能直接初始化其他string,比如strarray="new string"
无效
指针
因为我们都知道指针指向内存中的内存位置,所以未初始化的指针指向随机内存位置,所以初始化指向特定的内存位置
char *ptr = "hello";
这里指针ptr被初始化为string"hello"
,它是存储在只读存储器(ROM)中的常量string,所以"hello"
不能改变,因为它存储在ROM
和ptr存储在堆栈部分,并指向常量string"hello"
所以ptr [0] ='m'是无效的,因为你不能访问只读内存
但是ptr可以直接初始化为其他string值,因为它只是指针,所以它可以指向任何其数据types的variables的内存地址
ptr="new string"; is valid
char *str = "string";
以上设置str
指向程序的二进制图像中硬编码的"string"
,该"string"
在内存中可能被标记为只读。
所以str[0]=
正在尝试写入应用程序的只读代码。 我想这可能是编译器依赖。
char *str = "string";
分配一个指向string的指针,编译器将其放入可执行文件的不可修改部分;
char str[] = "string";
分配和初始化可修改的本地数组
@matli链接到的C常见问题解答提到它,但是这里没有其他人,所以为了澄清:如果在初始化字符数组之外的任何地方使用了string文本(在你的源代码中使用双引号的string)马克的第二个例子,它工作正常),该string是由编译器存储在一个特殊的静态string表 ,这是类似于创build一个全局静态variables(当然是只读的),实质上是匿名的(没有variables“名称“)。 只读部分是重要的部分,也是为什么@ Mark的第一个代码示例segfaults。
该
char *str = "string";
行定义了一个指针并将其指向一个文字string。 string不可写,所以当你这样做的时候:
str[0] = 'z';
你会得到一个seg故障。 在一些平台上,文字可能在可写内存中,所以你不会看到段错误,但是无效的代码(导致未定义的行为)。
该行:
char str[] = "string";
分配一个字符数组,并将string复制到该数组中,这是完全可写的,所以后续更新没有问题。
像“string”这样的string文字可能被分配在可执行文件的地址空间中作为只读数据(给出或带上你的编译器)。 当你去触摸它,它会发现你在泳衣区域,并让你知道一个seg故障。
在你的第一个例子中,你得到了一个指向那个const数据的指针。 在你的第二个例子中,你正在用一个常量数据的副本初始化一个由7个字符组成的数组。
// create a string constant like this - will be read only char *str_p; str_p = "String constant"; // create an array of characters like this char *arr_p; char arr[] = "String in an array"; arr_p = &arr[0]; // now we try to change a character in the array first, this will work *arr_p = 'E'; // lets try to change the first character of the string contant *str_p = 'G'; // this will result in a segmentation fault. Comment it out to work. /*----------------------------------------------------------------------------- * String constants can't be modified. A segmentation fault is the result, * because most operating systems will not allow a write * operation on read only memory. *-----------------------------------------------------------------------------*/ //print both strings to see if they have changed printf("%s\n", str_p); //print the string without a variable printf("%s\n", arr_p); //print the string, which is in an array.
首先, str
是一个指向"string"
的指针。 编译器允许将string文字放在不能写入的内存中,但只能读取。 (这真的应该引发了一个警告,因为你将一个const char *
分配给一个char *
。你是否禁用了警告,或者你是否忽略了它们?)
第二,你正在创build一个数组,这是你已经完全访问的内存,并用"string"
初始化它。 你正在创build一个char[7]
(六个字母,一个用于终止'\ 0'),你可以随心所欲地做任何事情。
首先是一个不能修改的常量string。 其次是一个具有初始化值的数组,因此可以修改。
当你访问不可访问的内存时会导致分段错误。
char *str
是一个指向不可修改的string(获取seg错误的原因)的指针。
而char str[]
是一个数组,可以修改..