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

StackOverflow https://stackoverflow.com/questions/164194

  •  03-07-2019
  •  | 
  •  

以下代码在第 2 行接收 seg 错误:

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 [] =" string literal";
   char * p =" string literal&quot ;;
  如果我尝试为 p [i] 分配一个新值,我的程序会崩溃。

     

A :字符串文字(正式用语   对于C中的双引号字符串   来源)可以稍微使用两个   不同的方式:

     
      
  1. 作为char数组的初始值设定项,如 char a [] 的声明,它指定初始值   该数组中的字符(和,   如果有必要,它的大小)。
  2.   
  3. 在其他地方,它变成一个未命名的静态字符数组,   并且可以存储该未命名的数组   在只读内存中,以及哪些内存   因此不一定如此   改性。在表达式上下文中,   数组一次转换为a   指针,像往常一样(见第6节),所以   第二个声明初始化p   指向未命名的数组的第一个   元件。
  4.         

    有些编译器有一个开关   控制是否为字符串文字   是否可写(用于编译旧的   代码),有些可能有选项   导致字符串文字正式   被视为const char数组(for   更好的错误捕获)。

其他提示

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

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

printf("%p", str);

此外,在第二个示例中打印 str 的大小将显示编译器为其分配了7个字节:

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

这些答案中的大多数都是正确的,但只是为了增加一点清晰度......

“只读存储器”。人们指的是ASM术语中的文本段。它是加载指令的内存中的相同位置。由于安全性等显而易见的原因,这是只读的。当您创建初始化为字符串的char *时,字符串数据将被编译到文本段中,程序会将指针初始化为指向文本段。因此,如果您尝试更改它,kaboom。段错误。

当编写为数组时,编译器会将初始化的字符串数据放在数据段中,这与全局变量等存在的位置相同。该内存是可变的,因为数据段中没有指令。这次编译器初始化字符数组(它仍然只是一个char *)时,它指向数据段而不是文本段,您可以在运行时安全地更改它。

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

C99 N1256草案

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

  1. 初始化 char[]:

    char c[] = "abc";      
    

    这是“更神奇的”,在 6.7.8/14“初始化”中进行了描述:

    字符类型的数组可以由字符串文本初始化(可选) 用大括号封闭。字符串文本的连续字符(包括 如果有空间或数组大小未知,则终止 null 字符) 初始化 数组的元素。

    所以这只是一个快捷方式:

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

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

  2. 其他地方:它生成一个:

    所以当你写:

    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 没有具体说明这些数组是否是不同的,前提是它们的元素具有 适当的值。如果程序尝试修改此类数组,则行为为 定义。

6.7.8/32“初始化”给出了一个直接的例子:

实施例8:宣言

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

定义“普通”char 数组对象 st 其元素用字符串文字初始化。

该声明等同于

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

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

char *p = "abc";

定义 p 类型为“指向 char 的指针”,并将其初始化为指向长度为 4 的“char 数组”类型的对象,该对象的元素使用字符串文字进行初始化。如果尝试使用 p 修改数组的内容,行为未定义。

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

在第一个代码中,“string”是字符串常量,永远不应修改字符串常量,因为它们通常放在只读内存中。 &QUOT; STR&QUOT;是一个用于修改常量的指针。

在第二个代码中,“string”是一个数组初始化器,是

的简写
char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

&QUOT; STR&QUOT;是在堆栈上分配的数组,可以自由修改。

因为第一个例子的上下文中的&quot; whatever&quot; 的类型是 const char * (即使你将它分配给非const char *) ,这意味着你不应该试着写信。

编译器通过将字符串放在内存的只读部分来强制执行此操作,因此写入它会生成段错误。

要了解此错误或问题,您应首先了解指针和数组的区别   所以这里首先我已经解释了你们之间的差异

字符串数组

 char strarray[] = "hello";

存储器阵列存储在连续存储器单元中,存储为 [h] [e] [l] [l] [o] [\ 0] =&gt; [] 为1个字节字节size memory memory,这个连续的内存单元可以通过名字命名为strarray来访问。这里的字符串数组 strarray 本身包含初始化为它的字符串的所有字符。在这种情况下&quot; hello&quot; ; 所以我们可以通过索引值

访问每个字符来轻松更改其内存内容
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

并将其值更改为'm',因此strarray值更改为&quot; mello&quot; ;

这里需要注意的一点是,我们可以通过逐字符更改字符串来更改字符串数组的内容,但不能像 strarray =&quot; new string&quot; 一样无法直接初始化其他字符串

指针

我们都知道指针指向内存中的内存位置, 未初始化的指针指向随机内存位置,因此在初始化后指向特定的内存位置

char *ptr = "hello";

此处指针ptr初始化为字符串&quot; hello&quot; ,这是存储在只读存储器(ROM)中的常量字符串,因此&quot; hello&quot; 不能更改为它存储在ROM

和ptr存储在堆栈部分并指向常量字符串&quot; hello&quot;

所以ptr [0] ='m'无效,因为你无法访问只读存储器

但是ptr可以直接初始化为其他字符串值,因为它只是指针,所以它可以指向其数据类型变量的任何内存地址

ptr="new string"; is valid
char *str = "string";  

上面设置 str 指向文字值&quot; string&quot; ,它在程序的二进制图像中被硬编码,可能被标记为只读在记忆中。

所以 str [0] = 正试图写入应用程序的只读代码。我猜这可能是编译器依赖的。

char *str = "string";

分配一个指向字符串文字的指针,编译器将其放入可执行文件的不可修改部分;

char str[] = "string";

分配并初始化一个可修改的本地数组

@matli链接的C FAQ提及它,但此处还没有其他人,所以为了澄清:如果在以外的任何地方使用字符串文字(源中的双引号字符串)初始化一个字符数组(即:@ Mark的第二个例子,它正常工作),该字符串由编译器存储在一个特殊的静态字符串表中,类似于创建一个全局静态变量(当然是只读的,基本上是匿名的(没有变量“name”)。 只读部分是重要的部分,也是@Mark的第一个代码示例段错误的原因。

 char *str = "string";

行定义指针并将其指向文字字符串。当您执行以下操作时,文字字符串是不可写的:

  str[0] = 'z';

你得到一个段错误。在某些平台上,文字可能在可写内存中,因此您不会看到段错误,但它是无效的代码(导致未定义的行为),无论如何。

该行:

char str[] = "string";

将字符数组和拷贝文字字符串分配到该数组中,该数组是完全可写的,因此后续更新没有问题。

字符串文字,例如&quot; string&quot;可能在您的可执行文件的地址空间中被分配为只读数据(给出或带走您的编译器)。当你去触摸它时,它会吓到你在游泳衣区域,并让你知道一个段错误。

在第一个示例中,您将获得指向该const数据的指针。在第二个示例中,您将使用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 是一个指向&quot; string&quot; 的指针。允许编译器将字符串文字放在内存中无法写入的位置,但只能读取。 (这确实应该触发警告,因为你将 const char * 分配给 char * 。你是否禁用了警告,或者你只是忽略了它们? )

第二,你要创建一个数组,这是你可以完全访问的内存,并用&quot; string&quot; 初始化它。你正在创建一个 char [7] (六个用于字母,一个用于终止'\ 0'),你用它做任何你想做的事。

假设字符串是,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

在第一种情况下,当'a'进入范围时,将复制文字。这里'a'是在堆栈上定义的数组。这意味着字符串将在堆栈上创建,其数据从代码(文本)内存中复制,代码(文本)内存通常是只读的(这是特定于实现的,编译器也可以将此只读程序数据放在可读写内存中) )。

在第二种情况下,p是在堆栈(本地范围)上定义的指针,并引用存储在其中的字符串文字(程序数据或文本)。通常修改这种记忆并不是好习惯,也不鼓励。

首先是一个不能修改的常量字符串。第二个是具有初始化值的数组,因此可以进行修改。

当您访问无法访问的内存时,会导致

分段错误。

char * str 是指向不可修改的字符串的指针(获取seg错误的原因)..

char str [] 是一个数组,可以修改..

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top