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

Mar*_*kus 277 c c-strings segmentation-fault

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

char *str = "string";
str[0] = 'z';  // could be also written as *str = 'z'
printf("%s\n", str);
Run Code Online (Sandbox Code Playgroud)

虽然这非常有效:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
Run Code Online (Sandbox Code Playgroud)

经过MSVC和GCC测试.

mat*_*tli 229

参见C FAQ,问题1.32

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

:字符串文字(C源代码中双引号字符串的正式术语)可以两种略有不同的方式使用:

  1. 作为char数组的初始化器,如声明中所示char a[],它指定该数组中字符的初始值(如果需要,还指定其大小).
  2. 在其他任何地方,它变成一个未命名的静态字符数组,这个未命名的数组可能存储在只读存储器中,因此不一定能被修改.在表达式上下文中,像往常一样将数组一次转换为指针(参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素.

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

  • 其他几点:(1)如所描述的那样发生段错误,但它的出现是运行环境的函数; 如果嵌入式系统中的代码相同,则写入可能没有效果,或者实际上可能将s更改为z.(2)因为字符串文字是不可写的,所以编译器可以通过将两个"string"实例放在同一个地方来节省空间.或者,如果代码中的其他地方有"另一个字符串",那么一块内存可以支持这两个文字.显然,如果允许代码更改这些字节,则可能会发生奇怪和困难的错误. (7认同)
  • 经过 2 年的 C++ 写作之后...TIL (3认同)
  • @rahul tyagi,不是临时数组。恰恰相反,它是寿命最长的数组。它由编译器创建并在可执行文件本身中找到。从上面你应该明白的是,它是一个*共享*数组,必须被视为*只读*(实际上可能是只读的)。 (3认同)

Gre*_*ill 99

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

在第二个例子中,字符串"string"复制从其只读家里的编译器str[]阵列.然后允许更改第一个字符.您可以通过打印每个地址来检查:

printf("%p", str);
Run Code Online (Sandbox Code Playgroud)

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

printf("%d", sizeof(str));
Run Code Online (Sandbox Code Playgroud)

  • 每当在printf上使用"%p"时,你应该将指针转换为void*,如printf("%p",(void*)str); 使用printf打印size_t时,如果使用最新的C标准(C99),则应使用"%zu". (12认同)
  • 此外,只有在获取类型的大小时才需要带有sizeof的括号(该参数看起来像一个转换).请记住,sizeof是一个运算符,而不是一个函数. (3认同)

Bob*_*ers 33

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

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

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


Cir*_*四事件 23

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

C99 N1256草案

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

  1. 初始化char[]:

    char c[] = "abc";      
    
    Run Code Online (Sandbox Code Playgroud)

    这是"更神奇",并在6.7.8/14"初始化"中描述:

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

    所以这只是一个捷径:

    char c[] = {'a', 'b', 'c', '\0'};
    
    Run Code Online (Sandbox Code Playgroud)

    像任何其他常规数组一样,c可以进行修改.

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

    所以当你写:

    char *c = "abc";
    
    Run Code Online (Sandbox Code Playgroud)

    这类似于:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;
    
    Run Code Online (Sandbox Code Playgroud)

    注意来自char[]to 的隐式转换char *,这总是合法的.

    然后,如果你修改c[0],你也修改__unnamed,这是UB.

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

    5在转换阶段7中,将值0的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列.然后使用多字节字符序列初始化静态存储持续时间和长度的数组,该数组足以包含序列.对于字符串文字,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化[...]

    6如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的.如果程序试图修改此类数组,则行为未定义.

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

例8:声明

char s[] = "abc", t[3] = "abc";
Run Code Online (Sandbox Code Playgroud)

定义"plain"char数组对象s,t其元素使用字符串文字初始化.

该声明与之相同

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Run Code Online (Sandbox Code Playgroud)

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

char *p = "abc";
Run Code Online (Sandbox Code Playgroud)

p使用"指向char的指针"类型定义并将其初始化为指向类型为"array of char"且长度为4的对象,其元素使用字符串文字初始化.如果尝试使用p修改数组的内容,则行为未定义.

GCC 4.8 x86-64 ELF实施

程序:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译和反编译:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Run Code Online (Sandbox Code Playgroud)

输出包含:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata
Run Code Online (Sandbox Code Playgroud)

结论:GCC将char*其存储在.rodata部分中,而不是存储在.text.

如果我们这样做char[]:

 char s[] = "abc";
Run Code Online (Sandbox Code Playgroud)

我们获得:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)
Run Code Online (Sandbox Code Playgroud)

所以它存储在堆栈中(相对于%rbp).

但是请注意,默认的链接脚本提出.rodata.text在同一网段,它具有执行,但没有写权限.这可以通过以下方式观察:

readelf -l a.out
Run Code Online (Sandbox Code Playgroud)

其中包含:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
Run Code Online (Sandbox Code Playgroud)


And*_*isi 17

在第一个代码中,"string"是一个字符串常量,并且永远不应修改字符串常量,因为它们通常被放入只读存储器中."str"是用于修改常量的指针.

在第二个代码中,"string"是一个数组初始化器,简而言之

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };
Run Code Online (Sandbox Code Playgroud)

"str"是在堆栈上分配的数组,可以自由修改.


小智 12

因为"whatever"第一个示例的上下文中的类型是const char *(即使您将其分配给非const char*),这意味着您不应该尝试写入它.

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


小智 8

要理解这个错误或问题你首先应该知道b/w指针和数组的区别所以首先我已经解释了你的差异b/w他们

字符串数组

 char strarray[] = "hello";
Run Code Online (Sandbox Code Playgroud)

在内存中,数组存储在连续的内存单元中,存储为[h][e][l][l][o][\0] =>[]1个字节大小的内存单元,这个连续的内存单元可以通过名称命名为strarray来访问.这里的字符串数组strarray本身包含初始化为它的字符串的所有字符.这里的情况是"hello" 这样我们可以通过索引值访问每个字符来轻松更改其内存内容

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
Run Code Online (Sandbox Code Playgroud)

并且它的价值变为'm'如此,以便将价值改变为"mello";

有一点这里要注意的是,我们可以通过改变性格改变字符字符串数组的内容,但不能直接初始化另一个字符串之如strarray="new string"无效

指针

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

char *ptr = "hello";
Run Code Online (Sandbox Code Playgroud)

这里指针ptr被初始化为字符串"hello",这是存储在只读存储器(ROM)中的常量字符串,因此"hello"无法更改,因为它存储在ROM中

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

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

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

ptr="new string"; is valid
Run Code Online (Sandbox Code Playgroud)


Dou*_*ugN 7

char *str = "string";  
Run Code Online (Sandbox Code Playgroud)

上面的设置str指向"string"在程序的二进制映像中硬编码的文字值,它可能在内存中标记为只读.

因此str[0]=,尝试写入应用程序的只读代码.我猜这可能是编译器依赖的.


Rob*_*ker 6

char *str = "string";
Run Code Online (Sandbox Code Playgroud)

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

char str[] = "string";
Run Code Online (Sandbox Code Playgroud)

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


rpj*_*rpj 6

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