众所周知,在 C++ 中,字符串文字是不可变的,修改字符串文字的结果是未定义的。例如
char * str = "Hello!";
str[1] = 'a';
这将带来未定义的行为。
除此之外,字符串文字被放置在静态内存中。所以它们存在于整个程序中。我想知道为什么字符串文字有这样的属性。
有几个不同的原因。
一种是允许将字符串文字存储在只读内存中(正如其他人已经提到的那样)。
另一个是允许合并字符串文字。如果一个程序在几个不同的地方使用相同的字符串字面量,最好允许(但不一定要求)编译器合并它们,这样您就可以获得指向同一内存的多个指针,而不是每个指针占用一块单独的内存。这也适用于两个字符串文字不一定相同但结尾相同的情况:
char *foo = "long string";
char *bar = "string";
在这种情况下,有可能bar
是foo+5
(如果我计算正确的话)。
在这两种情况下,如果您允许修改字符串文字,它可能会修改恰好具有相同内容的另一个字符串文字。同时,老实说,强制这样做也没有什么意义——拥有足够多的字符串文字可以重叠,大多数人可能希望编译器运行得更慢只是为了节省(也许)几十个字节,这是非常罕见的左右的记忆。
在编写第一个标准时,已经有编译器使用了所有这三种技术(可能还有一些其他技术)。由于无法描述您从修改字符串文字中获得的一种行为,而且显然没有人认为这是一项重要的支持能力,因此他们做了显而易见的事情:说即使尝试这样做也会导致未定义的行为。
修改文字是未定义的行为,因为标准是这样说的。标准这样说是为了允许编译器将文字放入只读内存中。它这样做有很多原因。其中之一是允许编译器优化只存储在源代码中重复多次的文字的一个实例。
我相信您会询问将文字放置在只读存储器中的原因,而不是有关链接器执行此操作的技术细节或禁止此类操作的标准的法律细节。
当字符串文字的修改起作用时,即使在没有字符串合并的情况下也会导致细微的错误(如果我们决定允许修改,我们有理由禁止)。当你看到像这样的代码
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 怎么办?字符串也有同样的问题。
这就是不允许在标准(不需要及早检测故障)和实际目标平台(提供检测潜在错误的好处)中修改字符串文字的原因。
我相信许多解释这件事的尝试都遭受了最糟糕的循环推理。该标准禁止写入文字,因为编译器可以合并字符串,或者可以将它们放置在只读内存中。它们被放置在只读存储器中以捕获违反标准的情况。合并文字是有效的,因为标准禁止......这是您要求的一种解释吗?
让我们看看其他语言。通用 Lisp 标准 对文字进行了未定义行为的修改,尽管之前 Lisp 的历史与 C 实现的历史非常不同。那是因为可写文字在逻辑上是 危险的。语言标准和内存布局仅反映了这一事实。
Python 语言只有一个地方可以发生类似于“写入文字”的事情:参数默认值,而这一事实一直让人们感到困惑。
您的问题已标记为C++
,我不确定它在隐式转换为 non-const方面的当前状态char*
:如果是转换,是否已弃用?我希望其他答案能够在这一点上提供完整的启示。当我们在这里谈论其他语言时,让我提一下普通 C。在这里,字符串文字不是const,并且要问的一个等效问题是为什么我不能修改字符串文字(而有更多经验的人会问,为什么是字符串如果我不能修改它们,文字非常量?)。但是,尽管存在这种差异,上述推理完全适用于 C。
因为是 K&R C,所以没有“const”这样的东西。在 ANSI C++ 之前也是如此。因此,有很多代码都有类似char * str = "Hello!";
的东西,如果标准委员会将文本文字设为 const,那么所有这些程序都将不再编译。于是他们做出了妥协。文本文字是官方的const char[]
,但它们有一个无声的隐式转换为char*
.
在 C++ 中,字符串文字是const
因为您不允许修改它们。在标准 C 中,它们也会const
如此,除了当const
被引入 C 时,有太多的代码char* p = "somethin";
使它们成为 const 会破坏,以至于它被认为是不可接受的。(C++ 委员会选择了一个不同的解决方案来解决这个问题,不推荐使用的隐式转换允许上述情况。)
在最初的 C 中,字符串文字不是const 的,并且是可变的,并且保证没有两个字符串文字共享任何内存。很快就意识到这是一个严重的错误,允许出现以下情况:
void
mutate(char* p)
{
static char c = 'a';
*p = a ++;
}
在另一个模块中:
mutate( "hello" ); // Can't trust what is written, can you.
(Some early implementations of Fortran had a similar issue,
where F(4)
might call F
with just about any integral value.
The Fortran committee fixed this, just like the C committee
fixed string literals in C.)