下面的代码在第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测试。


当前回答

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

c99n1256草案

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

Initialize char[]: char c[] = "abc"; This is "more magic", and described at 6.7.8/14 "Initialization": An array of character type may be initialized by a character string literal, optionally enclosed in braces. Successive characters of the character string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array. So this is just a shortcut for: char c[] = {'a', 'b', 'c', '\0'}; Like any other regular array, c can be modified. Everywhere else: it generates an: unnamed array of char What is the type of string literals in C and C++? with static storage that gives UB if modified So when you write: char *c = "abc"; This is similar to: /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; Note the implicit cast from char[] to char *, which is always legal. Then if you modify c[0], you also modify __unnamed, which is UB. This is documented at 6.4.5 "String literals": 5 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char, and are initialized with the individual bytes of the multibyte character sequence [...] 6 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

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

EXAMPLE 8: The declaration char s[] = "abc", t[3] = "abc"; defines "plain" char array objects s and t whose elements are initialized with character string literals. This declaration is identical to char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; The contents of the arrays are modifiable. On the other hand, the declaration char *p = "abc"; defines p with type "pointer to char" and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to modify the contents of the array, the behavior is undefined.

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* it存储在.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

其他回答

char *str = "string";

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

char str[] = "string";

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

// 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. 

在第一个代码中,"string"是一个字符串常量,字符串常量永远不应该被修改,因为它们通常被放置在只读内存中。"str"是一个用来修改常量的指针。

在第二段代码中,"string"是一个数组初始化器,类似于

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

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

要理解这个错误或问题,您应该首先了解指针和数组的差异b/w 所以在这里,我首先要解释一下它们的区别

字符串数组

 char strarray[] = "hello";

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

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

它的值变成了m所以strarray的值变成了mello;

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

指针

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

char *ptr = "hello";

这里的指针ptr被初始化为字符串“hello”,这是一个存储在只读存储器(ROM)中的常量字符串,所以“hello”不能被更改,因为它存储在ROM中

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

所以ptr[0]='m'是无效的,因为你不能访问只读内存

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

ptr="new string"; is valid

The C FAQ that @matli linked to mentions it, but no one else here has yet, so for clarification: if a string literal (double-quoted string in your source) is used anywhere other than to initialize a character array (ie: @Mark's second example, which works correctly), that string is stored by the compiler in a special static string table, which is akin to creating a global static variable (read-only, of course) that is essentially anonymous (has no variable "name"). The read-only part is the important part, and is why the @Mark's first code example segfaults.