下面的C字符数组存储实现背后的原因是什么?



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

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[]数组既存储在常量代码段中,也存储在函数堆栈中。在代码和代码中重复的原因是什么堆栈内存空间?为什么该值只能保存在堆栈中,如果它不是只读数据?

首先,让我们澄清一些事情。字符串字面值不一定是只读数据,只是尝试改变它们是未定义的行为。

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

这很可能源于标准尚未到位之前的一段时间(最初的ANSI/ISO命令是编纂现有的实践,而不是创建一种新的语言)。在许多实现中,字符串会为了效率而共享空间,例如代码:

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

导致:

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

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

你可以这样做的原因:

char indifferent[] = "meh";

是,当goodbad指向字符串字面值时,该语句实际上创建了一个足够大的字符数组来容纳"meh",然后数据复制到1中。数据的副本可以自由更改。

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

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

但是不管为什么,标准很清楚地说明了是什么。 From C11 6.4.5 String literals:

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

对于后一种情况,在6.7.6 Declarators6.7.9 Initialisation中有介绍。


1值得注意的是,这里适用的是正常的"as if"规则(只要一个实现表现得好像它遵循了标准,它就可以做它喜欢做的事情)。

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

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

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

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

这是一个实现细节,不应该与您有关:就ISO C而言,这些语句没有意义。这也意味着它可以以不同的方式实现。

当你

 char ch2[] = "World";

"World",这是一个字符串字面值,被复制到ch2,如果你使用malloc和指针,你最终会这样做。为什么会被复制呢?

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

通过复制它,你有了你自己的字符串副本(你"拥有"它),你可以随心所欲地修改它。

引用"美国信息系统程序设计语言C国家标准的基本原理"

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

这只是一个反例的部分答案,声称字符串字面值存储在只读内存中:

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

gcc -O6 -S cc

.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"
    call    printf
    ...

这里只实现了概念literal的语义;文字"world"根本不存在。

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

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

最新更新