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

What is the reason behind the following C char array storage implementation?

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

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[] 数组既存储在常量代码段中,也存储在函数堆栈中。 在代码和 堆栈内存space? 为什么这个值不是只读数据只能存栈?

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

不一定 必须崩溃,它可能工作得很好。但是,作为未定义的行为,如果您希望在另一个实现、同一实现的另一个版本甚至下周三编写 运行 代码,则不应依赖它。

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

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

导致:

good---------+
bad--+       |
     |       |
     V       V
   | u | n | s | u | c | c | e | s | s | f | u | l | [=11=] |

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

你可以这样做的原因是:

char indifferent[] = "meh";

是,虽然 goodbad 指向字符串文字,但该语句实际上创建了一个字符数组,其大小足以容纳 "meh" 然后 副本把数据放进去1。数据副本可自由更改

事实上,C99 基本原理文档明确引用了这个作为原因之一:

String literals are not required to be modifiable. This specification allows implementations to share copies of strings with identical text, to place string literals in read-only memory, and to perform certain optimizations.

但是不管为什么,标准在什么上已经很清楚了。 来自C11 6.4.5 String literals:

7/ 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.6 Declarators6.7.9 Initialisation 中有介绍。


1 尽管值得注意的是正常的 "as if" 规则适用于此(只要实现就像遵循标准一样,它就可以做它应该做的事)请)。

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

We will get Seg fault since the value is stored in the constant code segment

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

array is stored both in constant code segment but also in the function stack.

这是一个实现细节,您不必担心:就 ISO C 而言,这些陈述毫无意义。这也意味着它可以以不同的方式实现。

当你

 char ch2[] = "World";

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

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

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

引用 "Rationale for American National Standard for Information Systems Programming Language C"

String literals are specied to be unmodiable. This specication allows implementations to share copies of strings with identical text, to place string literals in read-only memory, and perform certain optimizations. However, string literals do not have the type array of const char, in order to avoid the problems of pointer type checking, particularly with library functions, since assigning a pointer to const char to a plain pointer to char is not valid.

这只是对字符串文字存储在只读存储器中的声明的反例的部分回答:

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    19438935, (%rsp)   ;; First four bytes in "worl"
    movw    0, 4(%rsp)         ;; next to bytes in "d[=11=]"
    call    printf
    ...

这里只实现了概念文字的语义;文字 "world[=27=]" 甚至不存在。

实际上只有当字符串字面量足够长时,优化编译器才会选择memcpy从字面量池中的数据入栈,要求字面量作为空终止字符串存在。

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