为什么在写入用“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术语)可以用两种稍微不同的方式使用:

  1. 作为char数组的初始化方法,如char a[]的声明中那样,它指定了该数组中字符的初始值(必要时还指定其大小)。
  2. 其他任何地方,它变成一个无名的,静态的字符数组,这个未命名的数组可以存储在只读存储器中,因此不一定会被修改。 在一个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草案

数组文字有两种完全不同的用法:

  1. 初始化char[]

     char c[] = "abc"; 

    这更“神奇”,并在6.7.8 / 14“初始化”中描述

    字符types的数组可以由string文字初始化,可选地用大括号括起来。 string文字的连续字符(包括终止空字符,如果有空间或数组未知大小)初始化数组的元素。

    所以这只是一个捷径:

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

    像任何其他常规数组一样, c可以被修改。

  2. 其他地方:它会产生:

    • 无名
    • 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"; 

定义“简单的”字符数组对象st其元素用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[]是一个数组,可以修改..