字符串字面值为什么是const的?

17

众所周知,在C++中字符串文字是不可变的,修改字符串文字的结果是未定义的。例如:

char * str = "Hello!";
str[1] = 'a';

这将导致未定义的行为。

此外,字符串字面量存储在静态内存中。因此它们在整个程序执行期间存在。我想知道为什么字符串字面量具有这样的属性。


6
不一定会出现运行时错误,这是未定义的行为。这意味着它可以做“任何事情”。 - Flexo
1
因为根据当前的链接脚本,它们可能会被放置在ROM中。 - πάντα ῥεῖ
这种行为在C++03中已被弃用,在C++11中被禁止。 - ipc
@ipc 我尝试在Visual Studio 2012中(使用新的C++标准)编写此代码,但仍存在错误。 - nabroyan
我不熟悉Visual Studio,但如果它们达到了完全的C++11一致性,这段代码就不应该编译。 - ipc
5个回答

24

有几个不同的原因。

其中一个是为了允许将字符串字面量存储在只读内存中(正如其他人已经提到的那样)。

另一个是允许合并字符串字面量。如果一个程序在多个不同的地方使用相同的字符串字面量,允许(但不一定要求)编译器将它们合并起来很好,这样你就可以得到指向同一片内存的多个指针,而不是每个占用单独的内存块。当两个字符串字面量不一定相同但具有相同的结尾时,也可以应用此功能:

char *foo = "long string";
char *bar = "string";

在这种情况下,如果我数对了的话,bar可能是foo+5

在这两种情况中,如果允许修改字符串常量,它可能会修改具有相同内容的其他字符串常量。与此同时,实际上没有必要强制执行任何一种方式--拥有足够多的字符串常量可以重叠的情况非常罕见,大多数人可能希望编译器运行得更慢,仅仅为了节省(也许)几十个字节的内存。

在第一个标准编写时,已经有编译器使用了这三种技术(可能还有其他几种)。由于无法描述通过修改字符串常量获得的一种行为,而且显然没有人认为这是一个需要支持的重要功能,他们做出了明智的决定:即试图这样做将导致未定义的行为。


12

修改字面量是未定义行为,因为标准规定了这一点。标准之所以这样规定是为了允许编译器将字面量放入只读内存中。它之所以这样做有多种原因,其中之一是允许编译器进行优化,即在源代码中重复多次的字面量只存储一个实例。


3
我相信您询问的是为什么文字字面值被放置在只读内存中的原因,而不是关于链接器执行此操作的技术细节或标准禁止某些操作的法律细节。
当字符串字面值的修改“起作用”时,即使没有字符串合并(如果我们决定允许修改,则有理由禁止),也会导致微妙的错误。当您看到像下面这样的代码时:
char *str="Hello";
.../* some code, but str and str[...] are not modified */
printf("%s world\n", str);

很自然地,你会认为你 知道 将要被打印的内容, 因为在初始化和使用之间,str(以及其内容)没有在特定位置被修改。

然而,如果字符串字面值是可写的,你就不再 知道了: 在这段代码或深度嵌套的函数调用中,str[0] 可能会在稍后被覆盖, 并且当代码再次运行时

char *str="Hello";

不再保证str内容的任何内容。如我们所预期的那样,此初始化被实现为将链接时间中已知的地址移动到str的位置。它不检查str是否包含“Hello”,也不分配新副本。但是,我们理解这段代码是将str重置为“Hello”。很难克服这种自然理解,并且很难推断代码在没有保证的情况下。当您看到像x + 14这样的表达式时,如果您必须考虑14可能在其他代码中被覆盖,从而变为42会怎么样?字符串也有同样的问题。

这就是禁止修改字符串文字的原因,无论是在标准中(没有要求早期检测故障)还是在实际目标平台中(提供检测潜在错误的奖励)。

我相信许多试图解释这个问题的尝试都受到了最糟糕的循环推理的影响。标准禁止对字面量进行编写,因为编译器可以合并字符串,或者它们可以被放置在只读内存中。它们被放置在只读内存中是为了捕捉标准的违规行为。合并字面量是有效的,因为标准禁止...这是你要求的一种解释吗?
让我们看看其他语言。Common Lisp standard将修改字面量定义为未定义行为,尽管先前Lisp的历史与C实现的历史非常不同。那是因为可写字面量在逻辑上是危险的。语言标准和内存布局仅反映了这个事实。
Python语言只有一个类似于“编写字面量”的地方:参数默认值,这个事实经常使人困惑
您的问题被标记为C++,我不确定它在非常量char*的隐式转换方面的当前状态:如果是一种转换,它是否已过时?我希望其他回答能够完全解决这个问题。因为我们在谈论其他语言,在这里让我提到纯C。在这里,字符串字面值不是const,一个等效的问题是为什么我不能修改字符串字面值(有更多经验的人会问如果我不能修改它们,为什么字符串字面值不是const?)。然而,尽管存在这种差异,上述推理对C也是完全适用的。

C++ 从何时开始关心防止程序员自己“开枪打脚”?另外,为什么使用原始数组时不存在同样的问题:int arr[] = {1, 2, 3}; - AlwaysLearning

1
在C++中,字符串字面量是const的,因为你不允许修改它们。在标准C中,它们也应该是const的,但是当const被引入到C中时,有很多像char* p = "somethin";这样的代码,如果使它们成为常量,就会出现问题,这被认为是不可接受的。(C++委员会选择了不同的解决方案,使用了一个弃用的隐式转换来允许上述情况。)
在最初的C中,字符串字面量是是常量,是可变的,并且保证没有两个字符串字面量共享任何内存。很快就意识到这是一个严重的错误,允许出现以下情况:
void
mutate(char* p)
{
    static char c = 'a';
    *p = a ++;
}

在另一个模块中:

mutate( "hello" );  //  Can't trust what is written, can you.

一些早期的Fortran实现存在类似问题,其中F(4)可能会使用几乎任何整数值调用F。就像C委员会修复C中的字符串字面量一样,Fortran委员会也进行了修复。

1
因为是K&R C,所以没有"const"这样的东西。同样地,在ANSI C++之前也是如此。因此有很多代码像这样:char * str = "Hello!";如果标准委员会将文本字面值设为const,所有这些程序都将无法编译。所以他们做了一个妥协。文本字面值是官方的const char[],但它们具有静默的隐式转换到char*

C++11 想和你说句话。 - Yakk - Adam Nevraumont

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