以下C char数组存储实现背后的原因是什么?

时间:2015-08-20 06:41:53

标签: c language-design

以下char数组实现背后的实现原因是什么?

char *ch1 = "Hello"; // Read-only data
/* if we try ch1[1] = ch1[2]; 
we will get **Seg fault** since the value is stored in 
the constant code segment */

char ch2[] = "World"; // Read-write data
/* if we try ch2[1] = ch2[2]; will work. */

根据 Head first C (第73,74页)一书,ch2[]数组既存储在常量代码段中,也存储在函数堆栈中。 在代码和代码中重复这两者的原因是什么 堆栈内存空间? 如果值不是只读数据,为什么该值只能保存在堆栈中?

3 个答案:

答案 0 :(得分:7)

首先,让我们清楚一些事情。字符串文字必然是只读数据,它只是尝试更改它们的未定义行为。

但是,如果你希望你的代码在另一个实现,同一个实现的另一个版本,甚至下周三运行,你不应该依赖它。

这可能源于标准制定之前的时间(最初的ANSI / ISO授权是编纂现有的实践而不是创建新的语言)。在许多实现中,字符串将共享空间以提高效率,例如代码:

char *good = "successful";
char *bad = "unsuccessful";

导致:

good---------+
bad--+       |
     |       |
     V       V
   | u | n | s | u | c | c | e | s | s | f | u | l | \0 |

因此,如果您更改good中的一个字符,它也会更改bad

您可以使用以下内容执行此操作:

char indifferent[] = "meh";

是,当goodbad指向字符串文字时,该语句实际上会创建一个足以容纳"meh"然后副本的字符数组数据进入 1 。数据副本可以自由更改。

事实上,C99理由文件明确指出这是其中一个原因:

  

字符串文字不需要是可修改的。此规范允许实现共享具有相同文本的字符串副本,将字符串文字放在只读内存中,并执行某些优化。

但无论为什么,标准都非常清楚是什么。来自C11 6.4.5 String literals

  

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

对于后一种情况,6.7.6 Declarators6.7.9 Initialisation

涵盖了这一点

1 虽然值得注意的是正常的"好像"规则适用于此处(只要实施的行为就像它遵循标准一样,它可以做它喜欢的事情。)

换句话说,如果实现可以检测到您从未尝试更改数据,则可以非常愉快地绕过副本并使用原始数据。

答案 1 :(得分:3)

  

我们将获得 Seg fault ,因为该值存储在常量中   代码段

这是错误的:您的程序崩溃,因为它收到一个指示段违规(SIGSEGV)的信号,默认情况下会导致程序终止。但这不是主要原因。修改字符串文字是未定义的行为,无论它是否存储在只读段中,这比你想象的要广泛得多。

  

数组既存储在常量代码段中,也存储在函数中   叠加。

这是一个实现细节,不应该关注你:就ISO C而言,这些陈述毫无意义。这也意味着它可以以不同的方式实施。

当你

 char ch2[] = "World";

"World",这是一个字符串文字,被复制到ch2,如果你使用malloc和指针,你最终会做的事情。现在,为什么要复制?

这样做的一个原因可能是它会有所期待。如果你可以修改这样的字符串文字,如果代码的另一部分引用它并期望具有该值,该怎么办?拥有共享字符串文字是有效的,因为您可以在程序中共享它们并节省空间。

通过复制它,你有自己的字符串副本(你自己"它),你可以随意修改它。

引用"美国国家信息系统标准编程语言C"

的基本原理
  

字符串文字被指定为不可修改的。此规范允许实现共享具有相同文本的字符串副本,将字符串文字放在只读内存中,并执行某些优化。但是,字符串文字没有const char的类型数组,以避免指针类型检查的问题,特别是对于库函数,因为将指向const char的指针指向一个指向char的普通指针是无效的。

答案 2 :(得分:2)

对于声称字符串文字存储在只读存储器中的反例,这只是部分答案:

int main() {
   char a[]="World";
   printf("%s", a);
}

gcc -O6 -S c.c

.LC0:
    .string "%s"                  ;; String literal stored as expected
                                  ;; in read-only area within code
    ...
    movl    $1819438935, (%rsp)   ;; First four bytes in "worl"
    movw    $100, 4(%rsp)         ;; next to bytes in "d\0"
    call    printf
    ...

这里只实现了 literal 概念的语义;文字"世界\ 0"甚至不存在。

实际上,只有当字符串文字足够长时,优化编译器才会选择从文字池中的memcpy数据进行堆叠,要求将文字存在为空终止字符串。

char *ch1 = "Hello"; OTOH的语义要求在某处存在线性数组,其地址可以分配给指针ch1