【问题标题】:In class static const ODR在类中静态常量 ODR
【发布时间】:2015-12-25 02:03:09
【问题描述】:

staticconst 成员的类内初始化让我有点困惑。例如,在下面的代码中:

#include <iostream>

struct Foo
{
    const static int n = 42;
};

// const int Foo::n; // No ODR

void f(const int& param)
{
    std::cout << param << std::endl;
}

int g(const int& param)
{
    return param;
}

template<int N>
void h()
{
    std::cout << N << std::endl;
}

int main()
{
    // f(Foo::n); // linker error, both g++/clang++
    std::cout << g(Foo::n) << std::endl; // OK in g++ only with -O(1,2 or 3) flag, why?!
    h<Foo::n>(); // this should be fine
}

Live example

我没有定义Foo::n(该行已注释)。所以,我希望调用f(Foo::n) 在链接时失败,确实如此。但是,每当我使用诸如-O1/2/3 之类的优化标志时,以下行std::cout &lt;&lt; g(Foo::n) &lt;&lt; std::endl; 只能通过gcc 编译和链接(clang 仍然会发出链接器错误)。

  1. 为什么 gcc(用 gcc5.2.0 和 gcc 4.9.3 尝试过)在优化开启时编译并链接代码?
  2. 我是否正确地说类内静态 const 成员的唯一用途是在常量表达式中,例如 h&lt;Foo::n&gt; 调用中的模板参数,在这种情况下代码应该链接?

【问题讨论】:

标签: c++ gcc linker one-definition-rule


【解决方案1】:

ODR 违规不需要诊断,来自草案 C++ 标准标准部分 3.2 [basic.def.odr](强调我的未来):

每个程序都应该包含每个非内联的定义 在该程序中使用 odr 的函数或变量; 无诊断 必需

因此,不同优化级别的不一致行为是完全一致的行为。

非正式的变量是odr-used 如果:

它的地址被占用,或者一个引用被绑定到它,如果一个函数被调用或者它的地址被占用,一个函数就是odr-used。如果一个对象或一个函数被odr-used,它的定义必须存在于程序的某个地方;违反此规定是链接时错误。

所以fg 都是odr-uses 并且需要定义。

关于 odr-use 的相关 C++14 引用来自 [basic.def.odr] 部分:

名称显示为潜在求值表达式 ex 的变量 x 被 ex 使用,除非应用 左值到右值的转换(4.1)到 x 产生一个常量表达式(5.19),它不会调用任何非平凡的 函数,并且,如果 x 是一个对象,ex 是表达式 e 的潜在结果集合中的一个元素, 其中左值到右值转换 (4.1) 应用于 e,或者 e 是丢弃值表达式 [...]

C++11 中的措辞类似,C++11 到 C++14 的变化体现在defect report 712

在 C++11 之前是a bit more complicated but in principle the same for this case

【讨论】:

    【解决方案2】:

    根本没有定义。 GCC 4.9.2 不会编译并将其与任何标志链接。

    请注意:

    const static int n = 42;
    

    是一个声明初始化器,但不是一个定义

    【讨论】:

    • 我知道这不是定义,但是正如您从现场示例中看到的那样,代码已编译和链接。
    • 这很奇怪。我没有 gcc4.9.2,但有来自 OS X 上 macports 的 4.9.3,它可以编译和链接。
    • 可能是优化器问题,但仍然没有定义。
    • 看这里:melpon.org/wandbox/permlink/o9HLkpgJAyibg36t 那是 4.9.2,它可以链接。是的,我知道这没有定义,这就是为什么我问为什么 gcc 编译并链接代码
    • 这个答案没有抓住重点。在某些情况下,工具链不关心缺少的定义,要么是因为未调用 ODR,要么是因为优化使其没有实际意义。 OP 遇到了其中一种情况,并想具体了解它是什么(尽管就个人而言,我不会费心试图合理化它)。
    【解决方案3】:

    正式而言,ODR 违规是未定义的行为,因此编译器可能会表现出它喜欢的任何行为。这就是行为随着优化级别和编译器而变化的原因——编译器没有义务维护特定的行为。

    【讨论】:

      【解决方案4】:

      我假设编译器在优化过程中执行了以下操作:

      • const static int n 到处内联。没有为变量n 分配内存,对它的引用变得无效。函数f() 需要引用n 所以程序不会被编译。

      • 函数g 很简短。它被有效地内联和优化。优化后g函数不需要引用n,它只返回常量值42。

      解决办法是在类外定义变量:

      struct Foo
      {
          const static int n;
      };
      
      const int Foo::n = 42;
      

      【讨论】:

      • 替代解决方案是使其成为constexpr 或枚举值。 constexpr 可以说更好,现在编译器普遍支持它,因为它的源代码更少,而且它在头文件中工作得很好。
      猜你喜欢
      • 1970-01-01
      • 2014-12-25
      • 1970-01-01
      • 2010-09-09
      • 2015-12-15
      • 2016-07-30
      • 1970-01-01
      相关资源
      最近更新 更多