繁体   English   中英

C / C ++:优化字符串常量的指针

[英]C/C++: Optimization of pointers to string constants

看一下这段代码:

#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 ),所有指针都指向同一位置。

编译器是否总是优化得如此之多,以至于它会搜索所有字符串常量以查看它们是否相等? 可以依靠这种行为吗?

它不能依靠,它是一种优化,不是任何标准的一部分。

我将代码的相应行更改为:

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)在所有后续子字符串中重用了第一个字符串。 由编译器选择如何在内存中安排字符串常量。

这是一个非常容易的优化,可能是如此之多,以至于大多数编译器作者甚至根本没有考虑过优化。 毕竟,将优化标志设置为最低级别并不意味着“完全幼稚”。

编译器在合并重复的字符串文字时的积极程度会有所不同。 它们可能将自己限制为一个子例程-将这四个声明放在不同的函数中而不是一个函数中,您可能会看到不同的结果。 其他人可能会做一个整个编译单元。 其他人可能依赖链接程序在多个编译单元之间进行进一步合并。

您不能依赖此行为,除非您的特定编译器的文档说可以。 语言本身在这方面没有要求。 即使不考虑可移植性,我也会警惕在自己的代码中依赖它,因为即使在单个供应商的编译器的不同版本之间,行为也很容易改变。

您当然不应该依赖该行为,但是大多数编译器都会这样做。 任何文字值(“ Hello”,42等)将存储一次,并且指向它的任何指针自然会解析为该单个引用。

如果发现需要依靠它,请确保安全并重新编码,如下所示:

char *watchmen = "Watchmen";
char *foo = watchmen;
char *bar = watchmen;

当然,您不应该指望这一点。 优化器可能会对您造成棘手的影响,应该允许这样做。

但是,这是常见的。 我记得在87年前,一个同学正在使用DEC C编译器,并且遇到了这个奇怪的错误,他所有的文字3都变成了11(数字可能已经更改以保护无辜者)。 他甚至进行了printf ("%d\\n", 3)并打印了11.

他打电话给我,是因为它太奇怪了(为什么让人们想到我?),大约30分钟的头部挠挠后,我们找到了原因。 这行大致是这样的:

if (3 = x) break;

注意单个“ =”字符。 是的,那是一个错字。 编译器有一个小错误,并允许这样做。 结果是将整个程序中所有他的文字3变成当时x中的任何东西。

无论如何,很明显,C编译器会将所有文字3放在同一位置。 如果80年代的C编译器能够做到这一点,那就太难了。 我希望它会很常见。

我不会依赖这种行为,因为我怀疑C或C ++标准是否会明确表明这种行为,但是编译器确实会这样做。 即使没有为编译器指定任何优化,它也表现出这种行为也很有意义。 没有任何权衡。

C或C ++中的所有字符串文字(例如“字符串文字”)都是只读的,因此是常量。 当你说:

char *s = "literal";

从某种意义上讲,您是在将字符串转换为非const类型。 但是,您无法删除字符串的只读属性:如果尝试操作它,则会在运行时而不是编译时被捕获。 (将字符串文字分配给您的变量时,实际上是使用const char *的充分理由。)

不,不能依靠它,但是将只读字符串常量存储在池中是一种非常简单而有效的优化。 只需存储按字母顺序排列的字符串列表,然后最后将它们输出到目标文件中即可。 考虑一个平均代码库中有多少个“ \\ n”或“”常量。

如果编译器希望获得更多效果,它也可以重复使用后缀:可以通过指向“ Hello \\ n”的最后一个字符来表示“ \\ n”。 但这可能并不会带来极大的复杂性增加。

无论如何,我不认为该标准能说明任何内容的存储位置。 这将是非常特定于实现的事情。 如果将其中两个声明放在单独的.cpp文件中,则情况也可能会发生变化(除非您的编译器进行了大量的链接工作)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM