以下字符数组实现背后的实现原因是什么?
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";
是,当good
和bad
指向字符串字面值时,该语句实际上创建了一个足够大的字符数组来容纳"meh"
,然后将数据复制到1中。数据的副本可以自由更改。
事实上,C99基本原理文档明确地引用了这一点作为原因之一:
字符串字面值不需要被修改。该规范允许实现共享具有相同文本的字符串副本,将字符串文字放在只读内存中,并执行某些优化。
但是不管为什么,标准很清楚地说明了是什么。 From C11 6.4.5 String literals
:
7/如果这些数组的元素有相应的值,则不指定这些数组是否不同。如果程序试图修改这样的数组,其行为是未定义的。
对于后一种情况,在6.7.6 Declarators
和6.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
。