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

10

那么字面量到底存储在哪里呢?(见下面的示例)

我无法修改字面量,因此它应该是一个const char*,尽管编译器让我使用char*,即使在大多数编译器标志下也没有警告。

而将const char*类型隐式转换为char*类型会给我一个警告,如下所示(在GCC上测试,但在VC++2010上表现类似)。

此外,如果我修改const char的值(通过下面的技巧,在这种情况下GCC最好给我一个警告),它不会出错,甚至可以在GCC上修改和显示它(尽管我猜测它仍然是未定义行为,我想知道为什么它没有对字面量执行相同的操作)。这就是为什么我问字面量存储在哪里,常量通常存储在哪里

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 */

有人能否将标题澄清为“字符串字面值”? - Jens Gustedt
至于第二个例子,在Windows上,字面量位于一个写保护页面中,很可能与实际指令一起。您可以使用VirtualProtect来解除保护以验证它。 - riv
6个回答

12

C标准并不禁止对字符串字面量进行修改。它只是说,如果尝试修改,则其行为是未定义的。根据C99的解释,委员会中有人想让字符串字面量可修改,因此标准没有明确禁止。

请注意,在C++中情况不同。在C++中,字符串字面量是const char数组。但是,C++允许从const char*到char*的转换。这个特性已经被弃用了。


3
const char* 转换为 char* 是为了与 C 和 C 库保持兼容性而被包含在标准中的。在添加 const 到 C 之前和之后,许多库都采用只读 char* 参数。为了允许用户在文本常量(或指向文本常量的指针)上调用这些函数,因此添加了这种类型转换。 - David Rodríguez - dribeas
5
实际上,C标准禁止修改字符串字面量的行为是未定义而非实现定义;依赖于修改的代码不属于C语言,而是用类似于C但不兼容的语言编写的。 - Christoph

3
我不确定C/C++标准对于字符串的定义,但我可以确切地告诉您在MSVC中字符串字面量的实际情况。我相信其他编译器的表现也是类似的。
字符串字面量存储在const数据段中。它们的内存映射到进程地址空间。然而,它们所存储的内存页面是只读的(除非在运行时显式修改)。
但还有一些事情需要了解。并不是所有包含引号的C/C++表达式都具有相同的含义。让我们澄清一切。
const char* a = "test";

以上语句让编译器创建一个字符串常量 "test",链接器确保它将在可执行文件中。在函数体中,编译器生成一段代码,在堆栈上声明一个变量 a,并通过字符串常量 "test" 的地址进行初始化。

char* b = a;

在这里,您声明了另一个变量b,它在堆栈上获取a的值。由于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,但你还是修改了它。但这与处理器异常无关,因为d仍然驻留在可读/可写存储器中。
我想再添加一个案例:
char b[] = "test";
b[2] = 'o';

以上代码在栈上声明了一个数组,并用字符串“test”进行初始化。它位于读/写内存中,可以被修改。这里没有问题。


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

如果字符串字面值表现出与复合字面值相同的行为,则更加一致,但由于这些是C99结构,并且需要保持向后兼容性,因此这不是一个选项,因此字符串字面值仍然是一个特殊情况。


1
字面值在内存中的确切位置在哪里?(参见以下示例)
已初始化数据段。在Linux上,它可以是.data或.rodata。
我无法修改文字,因此它应该是const char*,尽管编译器让我使用char*,即使使用大多数编译器标志也没有警告。
由于历史原因,如其他人已经解释过的那样。大多数编译器都允许您通过命令行选项告诉字符串常量是只读还是可修改的。
通常希望字符串文字为只读的原因是内存中只读数据段可以(并且通常是)在从可执行文件启动的所有进程之间共享。这显然可以释放一些RAM,以避免保留同一信息的冗余副本。

0
我没有任何警告,即使使用了大多数编译器标志。
真的吗?当我编译以下代码片段时:
int main()
{
    char* p = "some literal";
}

在 g++ 4.5.0 上,即使没有任何标志,我也会收到以下警告:

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


哦?太好了,他们在最新版本中添加了警告,我使用的是旧版本GCC 3.2,其中没有警告。在VC++2010上也没有警告。 - Dpp
3
你为什么要使用这么旧的GCC版本? - fredoverflow
3
你是在使用gcc还是g++?警告在C++(g++)中是有效的,但在C(gcc)中无效。 - Pete Kirkham

0

你可以写入c,因为你没有将它定义为const。将c定义为const是正确的做法,因为右侧的类型为const char*

它在运行时生成错误,因为“test”值可能被分配到只读代码段中。请参见这里这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接