字符串字面量跨越翻译单元的地址

7
我想问一下,在跨翻译单元时是否可以依赖于字符串字面量地址?例如:

一个名为foo.c的文件引用了一个字符串字面量"I'm a literal!",在另一个文件中(例如bar.c),是否可以依赖于相同的字符串字面量"I'm a literal!"将具有相同的内存地址?考虑到每个文件都将被翻译为单独的.o文件。

为了更好地说明,以下是示例代码:

# File foo.c
/* ... */
const char * x = "I'm a literal!"

# File bar.c
/* ... */
const char * y = "I'm a literal!"

# File test.c
/* ... */
extern const char * x;
extern const char * y;
assert (x == y); //Is this assertion going to fail?

以下是gcc的命令行示例:

gcc -c -o foo.o -Wall foo.c
gcc -c -o bar.o -Wall bar.c
gcc -c -o test.o -Wall test.c
gcc -o test foo.o bar.o test.o

如果字符串字面量在同一个翻译单元中,那么这样做可靠吗?


8
不可以依靠这个。 - Neil Kirk
2
@fanl - Visual C++编译器套件有一个“字符串池”选项,其中相同的文字字面量将具有相同的指针值。上述句子中的关键词是选项。这意味着您不能依赖于指针值是否相等。 - PaulMcKenzie
1
@Alnitak 字符串字面量不是常量字符串吗? - Neil Kirk
@NeilKirk 这取决于编程语言。在C++中,字符串字面值的类型为char const[],并且是一个const对象。在C中,字符串字面值的类型为char[]。但是有一条特殊规则,即不允许修改它,否则会导致未定义的行为。 - James Kanze
我忘记了是否允许这样做,但是对于非const字符串,我相信程序员可以覆盖字符串的内容(但可能会导致未定义的行为),如果它是池化的,则无法正常工作。 - Alnitak
显示剩余6条评论
2个回答

13
你不能依赖相同的字符串字面量具有相同的内存位置,这是一种实现决策。C99 draft standard 告诉我们,是否将相同的字符串字面量视为不同是未指定的,来自第 6.4.5String literals

如果它们的元素具有适当的值,则未指定这些数组是否不同。如果程序试图修改这样的数组,则行为未定义。

对于 C++,这在草案标准的第 2.14.5String literals 中得到了涵盖,其中说:

所有字符串字面量是否都不同(即存储在不重叠的对象中)是由实现定义的。尝试修改字符串字面量的效果是未定义的。

编译器允许池化字符串字面量,但您需要了解不同编译器之间的工作方式,因此这不是可移植的,并且可能会发生变化。Visual Studio包括一个用于字符串字面量池化的选项
在某些情况下,相同的字符串字面量可以进行池化以节省可执行文件中的空间。在字符串字面量池化中,编译器使所有对特定字符串字面量的引用指向内存中的同一位置,而不是每个引用都指向字符串字面量的单独实例。要启用字符串池化,请使用/GF编译器选项。
请注意,它确实符合在某些情况下的条件。 gcc支持跨编译单元的池化,您可以通过-fmerge-constants打开它:
尝试在编译单元之间合并相同的常量(字符串常量和浮点数常量)。如果汇编器和链接器支持,则此选项为优化编译的默认设置。使用-fno-merge-constants来阻止此行为。请注意,“attempt”和“if...support it”的用法。
至于C语言不要求将“字符串字面值”放入池中的理由,我们可以从存档的comp.std.c讨论字符串字面值中看到,这是由于当时实现的广泛多样性所致。
GCC可以作为示例,但不是动机。部分想在ROMable数据中拥有字符串字面值的原因是为了支持ROMming。我模糊地记得,在做出X3J11决定之前,曾使用过几个C实现,其中字符串字面值要么自动合并,要么存储在常数数据程序段中。考虑到现有的实践多样性和在需要时可用的简单解决方法,最好不要试图保证字符串字面值的唯一性和可写性。

1
你的意思是,期望地址在翻译单元之间和内部是相同的,这两种情况都不可靠吗? - Felipe Lavratti
2
@fanl 这就是他说的。在最初的 K&R C 中,每个字符串字面值都保证是唯一的,具有不同的地址(并且您可以修改一个字符串字面值);原始的 C 标准改变了这一点,并允许相同的字符串字面值具有相同的地址。 - James Kanze
@fanl 在所有情况下都不可靠,即使编译器支持池化,无论是 gcc 还是 Visual Studio 都在其文档中指出它仅适用于某些情况 - Shafik Yaghmour

4
不行,你不能期望有相同的地址。如果发生了,那就是偶然的。但没有强制性规定。
§ 2.14.5/p12
是否所有字符串字面量都是不同的(也就是说,存储在不重叠的对象中)是实现定义的。试图修改字符串字面量的效果是未定义的。
编译器可以任意处理。如果它们在不同的翻译单元中或者即使它们在同一个翻译单元中,它们也可以存储在不同的地址中,而与它们是只读内存无关。
例如,在 MSVC 上,两种情况下的地址完全不同,但同样地,没有什么阻止编译器合并指针的值(甚至不管只读部分约束在哪里)。

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