为什么编译器允许字符串文字不是const?

内存中的文字究竟在哪里? (见下面的例子)

我不能修改文字,所以它应该是一个const char *,虽然编译器允许我使用char *,即使有大多数编译器标志也没有警告。

虽然const char *类型的隐式转换为char *类型给了我一个警告,见下文(在GCC上测试,但它在VC ++ 2010上表现相似)。

另外,如果我修改一个const char的值(下面有一个技巧,GCC会更好地给我一个警告),它没有给出错误,我甚至可以在GCC上修改并显示它(即使我猜它仍然是未定义的行为,我想知道为什么它没有对文字做同样的事情)。 这就是为什么我要问那些文字存储在哪里,以及哪些更常见的const应该存储?

const char* a = "test"; char* b = a; /* warning: initialization discards qualifiers from pointer target type (on gcc), error on VC++2k10 */ char *c = "test"; // no compile errors c[0] = 'p'; /* bus error when execution (we are not supposed to modify const anyway, so why can I and with no errors? And where is the literal stored for I have a "bus error"? I have 'access violation writing' on VC++2010 */ const char d = 'a'; *(char*)&d = 'b'; // no warnings (why not?) printf("%c", d); /* displays 'b' (why doesn't it do the same behavior as modifying a literal? It displays 'a' on VC++2010 */ 

C标准不禁止修改字符串文字。 它只是说如果尝试,行为是不确定的。 根据C99的理论基础,委员会中有人希望字符串文字可以修改,因此标准并没有明确禁止它。

请注意,C ++中的情况有所不同。 在C ++中,字符串文字是const char的数组。 但是,C ++允许从const char *到char *的转换。 不过,该function已被弃用。

主要是历史原因。 但请记住,它们有点合理:字符串文字没有char *类型,但char [N]其中N表示缓冲区的大小(否则, sizeof将无法按字符串文字的预期工作)并且可以用于初始化非const数组。 由于数组到指针和非constconst的隐式转换,您只能将它们分配给const指针。

如果字符串文字表现出与复合文字相同的行为,那将更加一致,但由于这些是C99构造并且必须保持向后兼容性,因此这不是一个选项,因此字符串文字保持特殊情况。

内存中的文字究竟在哪里? (见下面的例子)

初始化数据段。 在Linux上,它是.data.rodata

我不能修改文字,所以它应该是一个const char *,虽然编译器允许我使用char *,即使有大多数编译器标志也没有警告。

历史因为其他人已经解释过。 大多数编译器允许您通过命令行选项判断字符串文字是只读还是可修改。

通常希望将字符串文字设置为只读的原因是内存中具有只读数据的段可以(并且通常)在从可执行文件启动的所有进程之间共享。 这显然使一些RAM免于被浪费以保留相同信息的冗余副本。

我不确定C / C ++标准对字符串的影响。 但我可以准确地告诉MSVC中字符串文字实际发生了什么。 而且,我相信,其他编译器的行为类似。

字符串文字驻留在const数据部分中。 它们的内存映射到进程地址空间。 但是,它们存储的内存页面是ead-only(除非在运行期间明确修改)。

但是你应该知道更多的东西。 并非所有包含引号的C / C ++表达式都具有相同的含义。 让我们澄清一切。

 const char* a = "test"; 

上面的语句使编译器创建一个字符串文字“test”。 链接器确保它将在可执行文件中。 在函数体中,编译器生成一个代码,该代码在堆栈上声明一个变量a ,该变量由字符串文字“test”的地址初始化。

 char* b = a; 

在这里,您在堆栈上声明另一个变量b ,它获取a的值。 由于指向只读地址 – 所以b 。 偶数事实b没有const语义并不意味着你可以修改它指向的内容。

 char *c = "test"; // no compile errors c[0] = 'p'; 

以上生成访问冲突。 同样,缺少const并不代表机器级别的任何内容

 const char d = 'a'; *(char*)&d = 'b'; 

首先 – 上面与字符串文字无关。 ‘a’不是字符串。 这是一个角色。 这只是一个数字。 这就像写下面的内容:

 const int d = 55; *(int*)&d = 56; 

上面的代码使编译器变得愚蠢。 你说变量是const ,但你设法修改它。 但这与处理器exception无关,因为d仍然驻留在读/写存储器中。

我想补充一个案例:

 char b[] = "test"; b[2] = 'o'; 

上面在堆栈上声明了一个数组,并用字符串“test”初始化它。 它驻留在读/写内存中,可以修改。 这里没问题。

即使使用大多数编译器标志,我也没有警告

真? 当我编译以下代码片段时:

 int main() { char* p = "some literal"; } 

在g ++ 4.5.0 甚至没有任何标志的情况下 ,我收到以下警告:

警告:已弃用从字符串常量转换为’char *’

你可以写c因为你没有使它成为const。 将c定义为const将是正确的练习,因为右侧具有类型const char*

它在运行时生成错误,因为“test”值可能分配给只读的代码段。 看到这里和这里 。