未定义的对静态const int的引用

时间:2011-03-22 13:24:02

标签: c++ gcc

我今天遇到了一个有趣的问题。考虑这个简单的例子:

template <typename T>
void foo(const T & a) { /* code */ }

// This would also fail
// void foo(const int & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

int main()
{
   Bar b;
   b.func();
}

编译时出现错误:

Undefined reference to 'Bar::kConst'

现在,我很确定这是因为static const int没有在任何地方定义,这是有意的,因为根据我的理解,编译器应该能够在编译时进行替换而不需要定义。但是,由于该函数采用const int &参数,它似乎没有进行替换,而是更喜欢引用。我可以通过进行以下更改来解决此问题:

foo(static_cast<int>(kConst));

我相信这现在迫使编译器创建一个临时的int,然后传递一个引用,它可以在编译时成功完成。

我想知道这是故意的,还是我期望从gcc中得到太多能够处理这种情况?或者这是我出于某种原因不应该做的事情?

8 个答案:

答案 0 :(得分:60)

这是故意的,9.4.2 / 4说:

  

如果静态数据成员是const integral或const枚举类型,   它在课堂上的宣言   定义可以指定一个   恒定初始化器应该是一个   积分常数表达式(5.19)In   那种情况下,会员可以出现在   积分常数表达式。该   会员仍应在a。中定义   命名空间作用域,如果它在   程序

当你通过const引用传递静态数据成员时,你“使用”它,3.2 / 2:

  

可能会评估表达式   除非它出现在积分中   需要持续表达(见   5.19),是sizeof运算符的操作数(5.3.3),或者是操作数   typeid运算符和表达式   不指定左值   多态类型(5.2.8)。一个   对象或非重载函数是   如果其名称出现在a中,则使用   潜在评估的表达。

所以实际上,当你按价值或static_cast传递它时,你会“使用”它。只是GCC让你在一个案例中脱离困境而不是另一个案例。

[编辑:gcc正在应用来自C ++ 0x草案的规则:“一个变量或非重载函数,其名称显示为一个可能被评估的表达式是odr-used,除非它是一个满足出现要求的对象在常量表达式(5.19)中,立即应用左值到右值的转换(4.1)。“静态强制转换立即执行lvalue-rvalue转换,因此在C ++ 0x中它不是“使用”。]

const引用的实际问题是foo在获取其参数地址的权限范围内,并将其与例如来自另一个调用的参数的地址进行比较,存储在全局中。由于静态数据成员是唯一对象,这意味着如果从两个不同的TU调用foo(kConst),则在每种情况下传递的对象的地址必须相同。除非在一个(并且只有一个)TU中定义对象,否则AFAIK GCC不能安排。

好的,所以在这种情况下foo是一个模板,因此定义在所有TU中都是可见的,因此编译器理论上可以排除它对地址做任何事情的风险。但一般来说,你当然不应该使用不存在的对象的地址或引用; - )

答案 1 :(得分:22)

如果您正在使用类声明中的初始化程序编写静态const变量,那就好像您已经编写了

class Bar
{
      enum { kConst = 1 };
}

并且GCC会以同样的方式对待它,这意味着它没有地址。

正确的代码应该是

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;

答案 2 :(得分:12)

这是一个非常有效的案例。特别是因为 foo 可以是STL中的函数,例如 std :: count ,它将 const T&amp; 作为其第三个参数。

我花了很多时间试图理解链接器为什么会遇到这样的基本代码问题。

错误消息

  

未定义参考&#39; Bar :: kConst&#39;

告诉我们链接器找不到符号。

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst

我们可以从“&#39; U&#39; Bar :: kConst是未定义的。因此,当链接器尝试完成其工作时,它必须找到符号。但是你只能声明 kConst并且不要定义它。

C ++中的解决方案也是如下定义:

template <typename T>
void foo(const T & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

const int Bar::kConst;       // Definition <--FIX

int main()
{
   Bar b;
   b.func();
}

然后,您可以看到编译器将定义放在生成的目标文件中:

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst

现在,你可以看到&#39; R&#39;说它是在数据部分中定义的。

答案 3 :(得分:2)

g ++版本4.3.4接受此代码(请参阅this link)。但g ++版本4.4.0拒绝它。

答案 4 :(得分:1)

我认为这个C ++的人工制品意味着只要引用Bar::kConst,就会使用它的字面值。

这意味着在实践中没有变量可以作为参考点。

您可能必须这样做:

void func()
{
  int k = kConst;
  foo(k);
}

答案 5 :(得分:1)

简单技巧:在+传递函数之前使用kConst。这将阻止常量被引用,这样代码就不会生成对常量对象的链接器请求,但它将继续使用编译器时间常量值。

答案 6 :(得分:1)

C++17 中的正确方法是简单地创建变量 constexpr:

static constexpr int kConst = 1;

constexpr 静态数据成员是隐式内联的。

答案 7 :(得分:0)

我遇到了与Cloderic(三元运算符中的静态const:r = s ? kConst1 : kConst2)提到的相同的问题,但是它仅在关闭编译器优化(-O0而不是-Os)后才抱怨。发生在gcc-none-eabi 4.8.5上。