【问题标题】:C/C++: Optimization of pointers to string constantsC/C++:优化指向字符串常量的指针
【发布时间】:2010-10-15 23:11:45
【问题描述】:

看看这段代码:

#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)。

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

【问题讨论】:

  • 记住字符串常量是只读的。因此优化是完全有效的。如果您想要可编辑的字符串,则需要将它们声明为数组而不是指针。然后你会得到不同的地址。

标签: c++ c optimization string constants


【解决方案1】:

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

如果编译器想要更加花哨,它也可以重用后缀:“\n”可以通过指向“Hello\n”的最后一个字符来表示。但这可能对复杂性的显着增加几乎没有好处。

无论如何,我不相信该标准说明了任何东西的真正存储位置。这将是一个非常特定于实现的事情。如果您将其中两个声明放在单独的 .cpp 文件中,那么情况也可能会发生变化(除非您的编译器进行重要的链接工作。)

【讨论】:

    【解决方案2】:

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

    如果你发现你需要依赖它,那么安全并重新编码如下:

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

    【讨论】:

    • 它是const char* (pedantry ;))
    【解决方案3】:

    这是一个非常简单的优化,可能以至于大多数编译器编写者根本不认为它是一种优化。毕竟,将优化标志设置为最低级别并不意味着“完全天真”。

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

    你不能依赖这种行为,除非你的特定编译器的文档说你可以。语言本身在这方面没有任何要求。即使不考虑可移植性,我也会对在自己的代码中依赖它持谨慎态度,因为即使在单个供应商编译器的不同版本之间,行为也可能会发生变化。

    【讨论】:

    • 我喜欢你的说法。对我来说,“没有优化”只是意味着“不要做任何可能使调试变得更困难的事情”。
    • @T.E.D.:我不明白这如何使调试变得更加困难。毕竟指针仍然指向正确的字符串内容。
    【解决方案4】:

    我不会依赖这种行为,因为我怀疑 C 或 C++ 标准是否会明确这种行为,但编译器这样做是有道理的。即使没有为编译器指定任何优化,它也表现出这种行为也是有道理的;没有任何取舍。

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

    char *s = "literal";
    

    从某种意义上说,您将字符串向下转换为非常量类型。然而,你不能取消字符串的只读属性:如果你试图操纵它,你会在运行时而不是在编译时被捕获。 (这实际上是在将字符串文字分配给您的变量时使用 const char * 的一个很好的理由。)

    【讨论】:

      【解决方案5】:

      不能依赖它,它是一种优化,不属于任何标准。

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

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

      【讨论】:

      • 我在 Cygwin 上尝试使用 g++ v4.3.2,但没有看到所有指针都偏移到同一个字符串常量中的行为。
      • @Ash:我在 GCC 3.4.6 上再次尝试,出现了这种行为。你如何编译代码?启用优化 >=O1 时会出现该行为。
      • 好像是平台的问题。这次我在 Linux(而不是 Cygwin)上尝试了它,并看到了优化的行为。再次感谢您分享此信息 :-)
      【解决方案6】:

      你当然不应该指望它。优化器可能会对您做一些棘手的事情,应该允许这样做。

      然而,非常很常见。我记得在 87 年,一位同学在使用 DEC C 编译器时遇到了一个奇怪的错误,他所有的字面值 3 都变成了 11(数字可能已经改变以保护无辜者)。他甚至做了一个printf ("%d\n", 3) 并打印了11.

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

      if (3 = x) break;
      

      注意单个“=”字符。是的,那是一个错字。编译器有一个小错误并允许这样做。其效果是将他在整个程序中的所有字面值 3 变成当时恰好在 x 中的任何内容。

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

      【讨论】:

      • 哇——80 年代中后期的 DEC 编译器允许对文字进行赋值?
      • 我看到它发生了。如果学校没有补丁或其他什么,我不会感到惊讶。
      • 感谢您分享过去 Ted 的花絮。那太酷了! :-)
      • +1 最好的理由是不要写像 if (0 == var) 这样可怕的东西。
      猜你喜欢
      • 2020-03-25
      • 2011-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-16
      • 2021-06-28
      • 2015-06-22
      相关资源
      最近更新 更多