常量可以优化掉吗

Can a constant be optimized away

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

Yes, but only link-time optimisation is capable of doing this, because only the link stage can prove that the address is never taken throughout the whole program。而且,如果您正在制作一个共享库,那么它就在 table 之外。一般而言,您最好假设该对象可能 在您的 executable.

中占用 space

尽管如此,无论是否提供存储,如果初始化程序在同一翻译单元中可见,您当然可以期望编译器“内联”使用该值。您可以通过未能提供单独的定义来观察这一点,然后执行任何不涉及 ODR 使用的操作并注意您可能不会收到未定义的引用 link 错误。

答案是这取决于上下文

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

精简版

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

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

长版

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

  1. 常量是否有明确的存储支持,
  2. 常量是否存在于未命名的命名空间中,
  3. 常量是否具有非平凡,不可见 constructors/destructors,
  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。常量位于未命名的命名空间中 具有非平凡的构造函数/析构函数

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

或者:

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。常量定义为 inline (C++17) 并使用 ODR

如果符号是 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),但没有明确声明的存储支持——这意味着它们不能被 ODR 使用,除非声明为 inline.

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

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

Example on Compiler Explorer


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

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