segmentation-fault - c - 为什么在写入用“char * s”初始化的字符串时会出现分段错误?但不是“char s []”?

  显示原文与译文双语对照的内容

下面的代码在第 2行接收seg故障:


char *str ="string";


str[0] = 'z';//could be also written as *str = 'z'


printf("%sn", str);



虽然这工作很正常:


char str[] ="string";


str[0] = 'z';


printf("%sn", str);



使用 MSVC 和GCC测试。

时间:

请参见常见问题,问题 1.32.

Q: 这些初始化之间的区别是什么
char a[] ="string literal";
char *p ="string literal";
如果尝试为 p[i] 分配新值,程序会崩溃。

:字符串文本可以用两种略有不同的方式使用:

  • 作为char的array的初始化者,如在 char a[] 声明中,它指定了 array ( 如有必要,它的尺寸) 中字符的初始值。
  • 在它的他任何地方,它变成一个未命名的static array,这个未命名的array 可以存储在只读内存中。 在表达式上下文中,array 一次转换为指针,通常为( 请参阅节 6 ),因此第二个声明初始化p 指向 array 元素的未命名第一个。

有些编译器拥有 switch 控制字符串是否可以写或者不是( 编译旧代码),有些编译器可以将字符串形式化为常量字符的数组。

在运行程序时,字符串通常存储在只读内存中。 这是为了防止你意外更改字符串常量。 在第一个示例中,"string" 存储在只读内存中,*str 指向第一个字符。 当你试图将第一个字符更改为 'z' 时发生错误。

在第二个示例中,字符串 "string" 是由编译器从只读的主页复制到 str[] array 中的。 允许更改第一个字符。 你可以通过打印每个地址的地址来检查:

 
printf("%p", str);



 

另外,在第二个示例中输出 str的大小将显示编译器为它分配了 7个字节:


printf("%d", sizeof(str));



大多数答案是正确的,只是为了增加一点清晰。

人们所指的"只读内存"是ASM术语中的文本段。 在内存中,指令的加载位置是相同的。 由于安全原因,这是只读的。 创建初始化为字符串的char* 时,字符串数据被编译到文本段中,程序将指针初始化为指向文本段的指针。 所以如果你尝试改变它。 Segfault 。

编译器作为 array 编写时,编译器将初始化的字符串数据放置在数据段中。 由于数据段中没有指令,所以这个内存是可变的。 这时编译器初始化字符 array ( 它还是一个 char* ),它指向数据段而不是文本段,你可以在运行时安全地修改它。

写入字符串时,为什么会出现分段错误?

N1256草稿

字符串文字有两种不同的用法:

初始化 char[]:


char c[] ="abc"; 



这是"更多魔术",在 6.7.8/14"初始化"中描述:

字符类型的array 可以由字符串文本初始化,也可以用大括号括起来。 字符串文本( 。如果有房间或者 array 大小未知,则包括终止空字符)的连续字符初始化 array的元素。

因此,这只是一个快捷方式:


char c[] = {'a', 'b', 'c', ''};



其他常规 array 一样,c 可以修改。

其他地方:它生成一个:

所以当你写的时候:


char *c ="abc";



这类似于:


/* __unnamed is magic because modifying it gives UB. */


static char __unnamed[] ="abc";


char *c = __unnamed;



注意从 char[]char *的隐式转换,它总是合法的。

然后修改 c[0],也可以修改 __unnamed,它是。

这是在 6.4.5"字符串文字"文档中记录的:

5 在翻译阶段 7中,在由字符串文本或者文本组成的每个多字节字符序列中都附加了值0的字节或者代码。 然后使用多字节字符序列初始化 array 存储持续时间和长度的,该序列仅包含序列。 对于字符串文本,array 元素具有类型 char,并使用多字节字符序列 [... ]的单个字节初始化。

6 不指定这些数组是否具有明确的值,因为它们的元素具有适当的值。 如果程序试图修改这样的array,则行为是未定义的。

6.7.8/32"初始化"提供了一个直接示例:

示例 8: 声明


char s[] ="abc", t[3] ="abc";



定义"普通"字符 array 对象 st,这些对象的元素用字符串文本。

这里声明与


char s[] = { 'a', 'b', 'c', '' },


t[] = { 'a', 'b', 'c' };



数组的内容是可以修改的。 另一方面,声明


char *p ="abc";



用类型定义 p 并初始化它以指向具有长度为1的对象的对象,长度为 4,它的元素由字符串文本初始化。 如果试图使用 p 修改 array的内容,则该行为是未定义的。

x86-64 4.8 ELF实现

程序:


#include <stdio.h>



int main(void) {


 char *s ="abc";


 printf("%sn", 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将 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



...