为什么字符串文字是常量

Why are string literals const?

本文关键字:常量 文字 字符串 为什么      更新时间:2023-10-16

众所周知,在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 worldn", str);
这是一个

自然的结论,你知道要打印什么,因为str(及其内容)未在特定位置,在初始化和使用之间。

但是,如果字符串文本是可写的,则您一无所知更多: str[0] 可以稍后在此代码中或深度嵌套的函数调用,当代码再次运行时

char *str="Hello";

不会再保证任何关于str的内容。正如我们期望,此初始化实现为移动已知地址在链接时间进入一个地方str.它不会检查该str包含"Hello",并且不会分配它的新副本。然而我们将此代码理解为将str重置为"Hello"。很难克服这种自然的理解,很难推理代码,其中不保证。当您看到类似 x+14,如果你不得不考虑 14 可能被覆盖怎么办在其他代码中,所以它变成了 42?字符串也有同样的问题。

这就是不允许修改字符串文字的原因,两者都在标准(不要求及早检测故障)和实际目标平台(提供检测潜力的好处错误)。

我相信许多解释这件事的尝试都遭受了最糟糕的循环推理。该标准禁止写入文本,因为编译器可以合并字符串,或者可以放置字符串在只读内存中。它们被放置在只读内存中以捕获违反标准。合并文字是有效的,因为标准禁止...这是你要求的一种解释吗?

让我们看看其他语言。通用 Lisp 标准修改文字未定义的行为,即使以前的Lisps的历史与C的历史非常不同实现。这是因为可写文字在逻辑上是危险。语言标准和内存布局仅反映这一点事实。

Python语言正好有一个地方类似于"写入文字"可能发生:参数默认值,以及事实总是让人们感到困惑。

您的问题被标记为C++,我不确定其当前状态关于隐式转换为非常量char*:如果它是一个转换,是否已弃用?我希望其他答案能提供在这一点上完全启蒙。正如我们谈论其他语言时在这里,让我提一下普通的C。在这里,字符串文字不是常量,要问的一个等效问题是为什么我不能修改字符串字面(更有经验的人会问,为什么字符串文字非常量,如果我不能修改它们?然而,上述推理完全适用于 C,尽管存在这种差异。

因为是K&R C,所以没有"const"这样的东西。 在ANSI之前的C++也是如此。 因此,有很多代码具有类似char * str = "Hello!"; 如果标准委员会使文本文字成为常量,所有这些程序将不再编译。 所以他们做出了妥协。 文本文字是官方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.

(Fortran的一些早期实现也有类似的问题,F(4)可能会用几乎任何整数值来调用F。Fortran 委员会修复了这个问题,就像 C 委员会一样C 中的固定字符串文本。