【问题标题】:Can a constant be optimized away可以优化常量吗
【发布时间】:2020-07-09 16:20:48
【问题描述】:

如果我们有一个static const 类成员,其地址从未使用过,是否可以优化该成员并且不分配存储空间?

【问题讨论】:

  • 从 g++ output 来看,我假设是的。
  • 如果删除某些内容不会改变程序的行为,答案通常是“是”。
  • 可以优化掉一个从未使用过的常量。换一种说法,由于没有使用,所以不会出现在代码中。
  • @YvesDaoust:如果使用 as-if 规则,我会说是的。
  • @YvesDaoust:这个问题有两个答案。如果其中任何一个的回答令您满意,请记住选择正确的答案,或留下反馈,说明如何改进答案。

标签: c++ optimization memory-management one-definition-rule


【解决方案1】:

答案是取决于上下文

编译器必须遵循 as-if 规则,以确保程序的行为必须与未优化时的优化方式相同——因此它只能删除它知道不会影响语法有效代码行为的常量。

短版

简而言之,只有符合以下条件的常量才能被优化出来:

  • 常量是微不足道的,或者具有可见的构造函数/析构函数,不影响外部状态
    • 此效果是可传递的。如果构造函数/析构函数调用了不可见的函数,编译器必须假设调用可能会改变外部状态,因此很重要。
  • 不使用 ODR 常量,并且
  • 常量要么是匿名定义的(在未命名的命名空间中),要么是内联定义的

加长版

有几种情况会影响存储备份

  1. 是否已为常量提供显式存储支持,
  2. 常量是否存在于未命名的命名空间中,
  3. 常量是否具有非平凡、不可见构造函数/析构函数,
  4. 常量是否定义inline (C++17),
  5. 是否定义了常量inline (C++17) 并使用了ODR,
  6. 常量是否内联定义,但未提供存储支持

让我们分解这些不同的部分:

1。常量有明确的存储支持

如果您从一开始就定义存储支持,例如:

struct example {
    static const int value;
}; 
const int example::value = 5;

然后通常会有存储支持,因为编译器必须假设它最终会被 ODR 使用——即使常量是私有的。

Example on Compiler Explorer

2。常量位于未命名的命名空间中

但是,如果这些类型是在未命名的命名空间中定义的,这使它们成为翻译单元的匿名符号——那么缺乏使用可能会导致它被优化掉:

namespace {
    struct example {
        static const int value;
    }; 
    const int example::value = 5;
}

Example on Compiler Explorer.

这仅适用于普通的构造函数/析构函数,或编译器当前可以看到的构造函数/析构函数以优化指令。

3。常量在一个未命名的命名空间中具有非平凡的构造函数/析构函数

如果上面的常量有一个非平凡的构造函数或析构函数,或者一个不可见的构造函数/析构函数,则必须发出常量:

要么:

namespace {
struct actor{ 
    actor();  // not defined
    ~actor(); // not defined
};

class example {
    static const actor value;
};
const actor example::value{};
}

Example on Compiler Explorer

或:

extern int some_global;
namespace {
struct actor{ 
    actor(){ ::some_global = 5;} 
    ~actor(){ ::some_global = 10;}
};

class example {
    static const actor value;
};
const actor example::value{};
}

Example on Compiler Explorer

4。常量定义为inline (C++17)

如果您在 C++17 中定义了 inline static constinline static constexpr 值,则取决于它是否用于 ODR 来确定是否会发出符号。

无 ODR 使用 -- 无排放:

struct example {
    inline static const int value = 5;
};

void test()
{
    std::printf("%d", example::value);
}

Example on Compiler Explorer.

5。使用 ODR 定义常量 inline (C++17)

如果符号是 inline 但使用了 ODR,则必须发出存储支持。

ODR 使用 -- 排放:

struct example {
    inline static const int value = 5;
};

void consume(const int&);

void test()
{
    consume(example::value)
}

Example on Compiler Explorer.

6。常量是内联定义的,但没有提供存储支持

如果您有静态 const 对象的内联定义(没有 C++17 的 inline),则永远不应该有存储支持。常量只能由常量表达式创建(有或没有constexpr),但没有明确声明的存储支持——这意味着除非声明inline,否则它们不能用于ODR。

这是 C++ 的类型特征所利用的,因为它们不产生任何额外的代码,因为它们相当于编译时常量对象。

struct example {
    static const int value = 5;
};

Example on Compiler Explorer


编辑:我想再补充一点:如果通过显式定义或通过使用 @987654347 的 ODR 为具有非内部链接的符号指定了任何存储支持@ 符号,编译器/链接器应该无法优化此退出。至少,不是来自对 as-if 规则的正确解释(尽管某些非标准优化标志可能允许这样做)。

问题在于编译器必须假设具有外部链接的符号仍然可以在其他地方命名或引用,即使该符号是private 并且永远无法通过friend-ship 访问。 C++ 有尖角,你可以reference private members in template parameters via explicit template specializations。因此,即使它在逻辑上不应该被访问,它实际上是从语言(以及编译器)的角度来看的。

【讨论】:

  • 哎哟!感谢您的广泛讨论。
  • 哦,我忘了static const inline
【解决方案2】:

Yes, but only link-time optimisation is capable of doing this,因为only the link stage can prove that the address is never taken throughout the whole program。而且,如果您正在制作一个共享库,那就不用考虑了。一般来说,您最好假设该对象可能占用您的可执行文件中的空间。

不管是否提供存储,如果初始化程序在同一个翻译单元中可见,您当然可以期望编译器“内联”使用该值。您可以通过未能提供单独的定义来观察这一点,然后做任何不涉及 ODR 使用的事情,并注意到您可能不会收到未定义的参考链接错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-05
    • 1970-01-01
    • 1970-01-01
    • 2021-04-24
    相关资源
    最近更新 更多