以下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[]
数组既存储在常量代码段中,也存储在函数堆栈中。
在代码和代码中重复这两者的原因是什么
堆栈内存空间?
如果值不是只读数据,为什么该值只能保存在堆栈中?
答案 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";
是,当good
和bad
指向字符串文字时,该语句实际上会创建一个足以容纳"meh"
然后副本的字符数组数据进入 1 。数据副本可以自由更改。
事实上,C99理由文件明确指出这是其中一个原因:
字符串文字不需要是可修改的。此规范允许实现共享具有相同文本的字符串副本,将字符串文字放在只读内存中,并执行某些优化。
但无论为什么,标准都非常清楚是什么。来自C11 6.4.5 String literals
:
7 /如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的。如果程序试图修改这样的数组,则行为是未定义的。
对于后一种情况,6.7.6 Declarators
和6.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
。