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


当前回答

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

其他回答

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.

因为在第一个例子的上下文中,“whatever”的类型是const char*(即使你将它赋值给一个非const char*),这意味着你不应该尝试写它。

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

不变的记忆

由于字符串字面量在设计上是只读的,所以它们存储在内存的Constant部分。存储在那里的数据是不可变的,即不能被更改。因此,在C代码中定义的所有字符串字面值在这里都获得一个只读内存地址。

栈内存

内存的堆栈部分是存放局部变量地址的地方,例如,函数中定义的变量。


正如@matli的回答所暗示的,有两种方法来处理这些常量字符串。

1. 指向字符串字面量的指针

当我们定义指向字符串字面量的指针时,我们是在Stack内存中创建一个指针变量。它指向底层字符串字面值所在的只读地址。

#include <stdio.h>

int main(void) {
  char *s = "hello";
  printf("%p\n", &s);  // Prints a read-only address, e.g. 0x7ffc8e224620
  return 0;
}

如果我们试图通过插入来修改s

s[0] = 'H';

我们得到一个分割错误(核心转储)。我们试图访问不应该访问的内存。我们正在尝试修改只读地址0x7ffc8e224620的值。

2. 字符数组

对于示例而言,假设存储在常量内存中的字符串字面值“Hello”具有与上述地址相同的只读内存地址0x7ffc8e224620。

#include <stdio.h>

int main(void) {
  // We create an array from a string literal with address 0x7ffc8e224620.
  // C initializes an array variable in the stack, let's give it address
  // 0x7ffc7a9a9db2.
  // C then copies the read-only value from 0x7ffc8e224620 into 
  // 0x7ffc7a9a9db2 to give us a local copy we can mutate.
  char a[] = "hello";

  // We can now mutate the local copy
  a[0] = 'H';

  printf("%p\n", &a);  // Prints the Stack address, e.g. 0x7ffc7a9a9db2
  printf("%s\n", a);   // Prints "Hello"

  return 0;
}

注意:当使用指针指向字符串字面量时,如1。,最好的做法是使用const关键字,如const *s = "hello"。这样可读性更强,并且当它被违反时,编译器将提供更好的帮助。然后它将抛出类似error:分配只读位置' *s '的错误,而不是seg错误。编辑器中的linter也可能在手动编译代码之前发现错误。

这些答案大部分都是正确的,但为了更清楚一点……

人们所说的“只读内存”是ASM术语中的文本段。它是内存中加载指令的同一个地方。出于安全等明显的原因,这是只读的。当创建一个初始化为字符串的char*时,字符串数据被编译到文本段中,程序初始化指向文本段的指针。所以如果你想改变它,就死定了。段错误。

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

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

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

printf("%p", str);

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

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