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

时间:2020-03-06 15:02:46  来源:igfitidea点击:

以下代码在第2行上收到段错误:

char *str = "string";
str[0] = 'z';
printf("%s\n", str);

尽管这很好用:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

经过MSVC和GCC测试。

解决方案

因为在第一个示例的上下文中,"无论如何"的类型都是" const char *"(即使我们将其分配给非const char *),这意味着我们不应该尝试对其进行写入。

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

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

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

printf("%p", str);

同样,在第二个示例中打印str的​​大小将显示编译器已为其分配7个字节:

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

char *str = "string";

上面的代码将" str"设置为指向文本值" string",该值在程序的二进制映像中进行了硬编码,可能在内存中被标记为只读。

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

在第一个代码中,"字符串"是一个字符串常量,永远不要修改字符串常量,因为它们经常被放置在只读存储器中。 " str"是用于修改常量的指针。

在第二个代码中," string"是一个数组初始值设定项,对于

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '
char *str = "string";
' };

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

char str[] = "string";

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

char *str = "string";

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

诸如" string"之类的字符串文字可能会作为只读数据(赋予或者接受编译器)分配在可执行文件的地址空间中。当我们触摸它时,它会吓到我们位于其泳衣区域,并通过段错误告知我们。

在第一个示例中,我们将获得指向该const数据的指针。在第二个示例中,我们将使用const数据的副本初始化7个字符的数组。

str[0] = 'z';

行定义了一个指针,并将其指向文字字符串。文字字符串不可写,因此在执行以下操作时:

char str[] = "string";

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

该行:

Q: What is the difference between these initializations?

  char a[] = "string literal";

  char *p  = "string literal";

  My program crashes if I try to assign a new value to p[i].
  
  A: A string literal (the formal term
  for a double-quoted string in C
  source) can be used in two slightly
  different ways:
  
  
  As the initializer for an array of char, as in the declaration of char a[] , it specifies the initial values
  of the characters in that array (and,
  if necessary, its size).
  Anywhere else, it turns into an unnamed, static array of characters,
  and this unnamed array may be stored
  in read-only memory, and which
  therefore cannot necessarily be
  modified. In an expression context,
  the array is converted at once to a
  pointer, as usual (see section 6), so
  the second declaration initializes p
  to point to the unnamed array's first
  element. 
  
  
  Some compilers have a switch
  controlling whether string literals
  are writable or not (for compiling old
  code), and some may have options to
  cause string literals to be formally
  treated as arrays of const char (for
  better error catching).

分配一个字符数组并将文字字符串复制到该数组中,这是完全可写的,因此后续更新没有问题。

请参阅C FAQ,问题1.32

##代码##

首先," str"是一个指向"字符串"的指针。允许编译器将字符串文字放在不能写入但只能读取的内存中。 (这确实应该触发警告,因为我们正在将const char *分配给char *。我们禁用了警告,还是只是忽略了它们?)

其次,我们要创建一个数组,该数组是我们具有完全访问权限的内存,并使用" string"对其进行初始化。我们正在创建一个" char [7]"(六个代表字母,一个代表终止字母" \ 0"),然后我们可以随心所欲地对其进行处理。

@matli链接到的C FAQ提到了它,但是这里还没有人提到,因此为了澄清:是否在初始化字符数组(例如:@)之外的任何地方都使用了字符串文字(源中双引号的字符串)。 Mark的第二个示例正确运行),该字符串由编译器存储在一个特殊的静态字符串表中,类似于创建一个基本上是匿名的全局静态变量(当然是只读的)(没有变量" name ")。只读部分是重要的部分,也是@Mark的第一个代码示例segfaults的原因。

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

人们所指的"只读内存"是ASM术语中的文本段。指令在内存中位于同一位置。出于安全性等明显原因,这是只读的。当我们创建一个初始化为字符串的char *时,字符串数据将被编译到文本段中,并且程序会初始化指针以指向该文本段。因此,如果我们尝试更改它,请kaboom。 Segfault。

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