C / C ++:优化指向string常量的指针
看看这个代码:
#include <iostream> using namespace std; int main() { const char* str0 = "Watchmen"; const char* str1 = "Watchmen"; char* str2 = "Watchmen"; char* str3 = "Watchmen"; cerr << static_cast<void*>( const_cast<char*>( str0 ) ) << endl; cerr << static_cast<void*>( const_cast<char*>( str1 ) ) << endl; cerr << static_cast<void*>( str2 ) << endl; cerr << static_cast<void*>( str3 ) << endl; return 0; }
其中产生这样的输出:
0x443000 0x443000 0x443000 0x443000
这是在Cygwin下运行的g ++编译器。 即使没有开启优化( -O0
),指针也指向相同的位置。
编译器是否总是优化得如此之多以至于它search所有的string常量以查看它们是否相等? 这种行为可以依靠吗?
这是一个非常容易的优化,可能是如此之多,以至于大多数编译器作者甚至不认为它是一个很好的优化。 毕竟,把优化标志设置到最低水平并不意味着“完全天真”。
编译器会在合并重复的string文字方面有所不同。 他们可能会把自己限制在一个子程序中 – 把这四个声明放在不同的函数中,而不是一个函数,你可能会看到不同的结果。 其他人可能会做一个完整的编辑单位。 其他人可能依靠链接器进一步合并多个编译单元。
你不能依赖这种行为,除非你的特定编译器的文档说你可以。 语言本身在这方面没有要求。 即使可移植性不是一个问题,我依然会在自己的代码中依赖它,因为甚至在单个供应商的编译器的不同版本之间,行为也可能发生变化。
它不能被依赖,这是一个不属于任何标准的优化。
我已经改变了你的代码的相应的行:
const char* str0 = "Watchmen"; const char* str1 = "atchmen"; char* str2 = "tchmen"; char* str3 = "chmen";
-O0优化级别的输出是:
0x8048830 0x8048839 0x8048841 0x8048848
但是对于-O1来说:
0x80487c0 0x80487c1 0x80487c2 0x80487c3
正如你可以看到GCC(v4.1.2)在所有后续的子string中重用了第一个string。 编译器select如何在内存中安排string常量。
你当然不应该依赖这种行为,但大多数编译器会这样做。 任何字面值(“你好”,42等)将被存储一次,任何指向它的指针自然会parsing为单一的引用。
如果你发现你需要依靠那个,那么安全并且重新编码如下:
char *watchmen = "Watchmen"; char *foo = watchmen; char *bar = watchmen;
你当然不应该指望这一点。 优化器可能会对你做一些棘手的事情,应该允许这样做。
然而,这是非常普遍的。 我记得在1987年,一个同学使用了DEC C编译器,并且有了这个奇怪的错误,他所有的文字3变成了11(数字可能改变了,以保护无辜者)。 他甚至做了一个printf ("%d\n", 3)
,并打印了11.
他打电话给我,因为它太奇怪了(为什么这会让人想起我?),大概30分钟后,我们find了原因。 这是一个粗略的线条:
if (3 = x) break;
请注意单个“=”字符。 是的,这是一个错字。 编译器有一个小错误,并允许这个。 其效果是将整个程序中的所有文字3变成当时在x中发生的事情。
无论如何,它清楚的C编译器将所有文字3放在同一个地方。 如果一个早在80年代的C编译器能够做到这一点,那么这样做不是太难。 我希望这是非常普遍的。
我不会依赖这种行为,因为我怀疑C或C ++标准是否会明确这种行为,但编译器是这样做的。 即使在没有为编译器指定任何优化的情况下,它也performance出这种行为也是有意义的。 它没有任何折衷。
C或C ++中的所有string文字(例如“string文字”)是只读的,因此是常量。 当你说:
char *s = "literal";
您在某种意义上将string向下转换为非consttypes。 不过,你不能废除string的只读属性:如果你试图操作它,你会在运行时而不是在编译时被捕获。 (当将string文字分配给你的variables时,这实际上是一个很好的理由。
不,它不能被依赖,但是在一个池中存储只读string常量是一个非常简单和有效的优化。 这只是存储一个按字母顺序排列的string列表,然后将其输出到目标文件中。 考虑一下平均代码库中有多less“\ n”或“”常量。
如果编译器想要获得额外的花式,也可以重复使用后缀:“\ n”可以通过指向“Hello \ n”的最后一个字符来表示。 但是,这可能会带来复杂性显着增加的好处。
无论如何,我不相信这个标准说的是什么东西真的存储在哪里。 这将是一个非常具体的实现。 如果你把两个这样的声明放在一个单独的.cpp文件中,那么情况也可能会改变(除非你的编译器做了重要的链接工作)。