为什么写有“字符* s”但不是“个char []”初始化字符串时,我得到一个分段错误?

问题描述 投票:263回答:17

下面的代码接收线2赛格故障:

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 segmentation-fault c-strings
17个回答
224
投票

看到C FAQ,Question 1.32

问:什么是这些初始化的区别? char a[] = "string literal"; char *p = "string literal"; 如果我尝试分配一个新值p[i]我的程序崩溃。

答:一个字符串字面量(正式在C源的双引号字符串项)可以在两个略有不同的使用方法:

  1. 作为初始值设定为字符数组,如在char a[]的声明时,它指定字符的初始值数组中(并且,如果必要的话,它的尺寸)。
  2. 在别的地方,它变成一个字符无名,静态数组,并且该未命名阵列可以被存储在只读存储器,并且因此可以不必进行修改。在表达式上下文中,阵列被转换一次的指针,像往常一样(参见第6节),所以第二个声明初始化p来指向无名数组的第一个元素。

一些编译器有一个开关控制字符串文字是否可写或不(编译旧代码),有的可有选择导致字符串文字作为常量字符数组正式处理(更好的错误捕获)。


6
投票

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


4
投票

 char *str = "string";

行定义了一个指针,它指向一个文字串。所以当你的文本字符串是不可写:

  str[0] = 'z';

你会得到一个赛格故障。在某些平台上,字面可能是可写存储器,所以你不会看到一个段错误,但它是无效的代码(导致不确定的行为),而不管。

该行:

char str[] = "string";

分配的字符和复制文字串成阵列,这是完全可写的阵列,所以随后的更新是没有问题的。


3
投票

像“串”字符串文字在你的可执行文件的地址空间分配可能为只读数据(给予或采取你的编译器)。当你去触摸它,它怪胎,你在它的游泳衣面积是,让你知道与赛格故障。

在你的第一个例子中,你得到一个指向常量数据。在你的第二个例子,你在初始化的7个字符用const数据的副本的阵列。


2
投票
// 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. 

1
投票

首先,str是一个指向"string"的指针。编译器允许把字符串字面量在内存中,你不能写的地方,但只能读取。 (这真的应该触发警告,因为你分配一个const char *char *。您有警告禁用,还是你只是忽略它们?)

其次,你要创建一个数组,这是记忆,你有完全访问,并与"string"初始化。你要创建一个char[7](六为字母,一个用于终止“\ 0”),你做任何你用它喜欢。


0
投票

假设字符串,

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

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

在第二种情况下,p是上堆叠(本地范围)所定义的一个指针并且参照其他地方存储一个字符串(程序数据或文本)。通常修改这样记性不好的做法,也不鼓励。


-1
投票

第一是一个常数字符串不能被修改。第二个是与初始化值的阵列,因此它可以被修改。


-2
投票

当您尝试访问是不可访问的内存分段故障引起的。

char *str是指向一个字符串,它是不可修改(用于获取赛格故障的原因)..

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


94
投票

通常情况下,字符串存储在只读存储器中运行该程序时。这是为了防止意外改变一个字符串常量。在你的第一个例子,"string"存储在只读存储器和*str指向第一个字符。当你试图改变第一个字符'z'的段错误发生。

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

printf("%p", str);

此外,印刷str的大小在第二个例子中会显示该编译器已经分配了7个字节为它:

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

31
投票

大多数这些问题的答案是正确的,但只是为了增加一点更清晰......

在“只读存储器”的人指的是在ASM条款文本段。它在内存中的同一个地方,指令被加载。这是只读的,如安全原因很明显。当您创建一个char *初始化字符串,字符串数据被编译成文本段和程序初始化指针指向到文本段。所以,如果你试图改变它,KABOOM。段错误。

当作为阵列写,编译器将初始化字符串数据的数据段,而不是,这是同一个地方,你的全局变量和这样的活。该存储器是可变的,因为有数据段没有说明。当编译器初始化字符数组这个时候(这仍然只是一个char *)它指向的数据段,而不是文本段,可以放心地改变在运行时。


21
投票

为什么写入字符串时,我得到一个分段错误?

C99 N1256草案

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

  1. 初始化char[]char c[] = "abc"; 这是“更神奇”,并且在6.7.8 / 14“初始化”中描述: 字符类型的阵列可以由字符串文字进行初始化,任选在大括号。的字符串文字的连续字符(包括终止空字符,如果有房间或如果阵列是未知大小的)初始化所述阵列的所述元件。 所以,这只是一个为快捷方式: char c[] = {'a', 'b', 'c', '\0'}; 像任何其他规则阵列,c可以被修改。
  2. 在其他地方:它产生: 无名 炭What is the type of string literals in C and C++?的阵列 与静态存储 给出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“字符串常量”: 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,类型为“字符指针”并对其进行初始化,以指向对象类型与长度为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

结论: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

17
投票

在第一个代码,“串”是一个字符串常量和字符串常量不应该被修改,因为他们往往是放在只读存储器。 “STR”是被用来修改恒定的指针。

在第二个码,“串”是一个数组初始化,排序的短手

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

“STR”是在栈上分配一个数组,并且可以自由地进行修改。


12
投票

由于"whatever"的第一示例的上下文中的类型是const char *(即使你把它分配给非为const char *),这意味着你不应该试图写它。

编译器已经投入的内存只读部分字符串,因此写它会产生一个段错误执行此操作。


8
投票

要理解这一点错误或问题,你应该先知道区别的B / W指针和数组所以在这里首先我必须解释一下你的B / W它们的差异

string array

 char strarray[] = "hello";

在存储器阵列存储在连续的存储单元,存储为[h][e][l][l][o][\0] =>[]是1个字符字节大小的存储单元,以及该连续的存储单元可以是由strarray命名here.so这里字符串数组strarray本身含有初始化为它字符串的所有字符名的访问。在这种情况下,这里"hello"所以我们可以很容易地通过它的索引值访问每个字符改变其存储内容

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

并将其值变更为'm'变为"mello"所以strarray值;

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

Pointer

大家都知道指针所指向的内存位置在内存中,未初始化的指针指向随机内存位置,以便以后初始化指向特定内存位置

char *ptr = "hello";

这里指针PTR被初始化为字符串"hello"其是存储在只读存储器(ROM)恒定串,以便将它存储在ROM "hello"不能改变

和PTR被存储在堆栈段和指向常数字符串"hello"

所以PTR [0] =“m”是无效的,因为你不能访问只读存储器

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

ptr="new string"; is valid

7
投票
char *str = "string";  

上述套str指向文字值"string"其被硬编码程序的二进制图像,这可能是标记为只读存储器英寸

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


6
投票
char *str = "string";

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

char str[] = "string";

分配和初始化一个本地阵列是可修改

© www.soinside.com 2019 - 2024. All rights reserved.