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

时间:2021-05-06 05:29:06

I ran into an interesting issue today. Consider this simple example:

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

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();
}

When compiling I get an error:

编译时出错:

Undefined reference to 'Bar::kConst'

Now, I'm pretty sure that this is because the static const int is not defined anywhere, which is intentional because according to my understanding the compiler should be able to make the replacement at compile-time and not need a definition. However, since the function takes a const int & parameter, it seems to be not making the substitution, and instead preferring a reference. I can resolve this issue by making the following change:

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

foo(static_cast<int>(kConst));

I believe this is now forcing the compiler to make a temporary int, and then pass a reference to that, which it can successfully do at compile time.

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

I was wondering if this was intentional, or am I expecting too much from gcc to be able to handle this case? Or is this something I shouldn't be doing for some reason?

我想知道这是故意的,还是我对gcc的期望太高了?还是因为某种原因我不应该这样做?

7 个解决方案

#1


53  

It's intentional, 9.4.2/4 says:

这是有意的,9.4.2/4说:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19) In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program

如果一个静态数据成员是const积分或const枚举类型,它在类定义中的声明可以指定一个常量初始化器,在这种情况下,它应该是一个完整的常量表达式(5.19),成员可以出现在积分常数表达式中。如果该成员在程序中使用,则仍然在名称空间范围内定义。

When you pass the static data member by const reference, you "use" it, 3.2/2:

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

An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19), is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.

表达式是有潜在价值的,除非它出现在需要积分常数表达式的地方(见5.19),它是sizeof运算符(5.3.3)的操作数,或者是typeid运算符的操作数,表达式没有指定多态类类型的lvalue(5.2.8)。如果一个对象或非重载函数的名称出现在一个潜在的表达式中,则使用它。

So in fact, you "use" it when you pass it by value too, or in a static_cast. It's just that GCC has let you off the hook in one case but not the other.

所以实际上,当你通过值传递它时,或者在static_cast中“使用”它。只是海湾合作委员会让你摆脱了困境,而不是另一个。

[Edit: gcc is applying the rules from C++0x drafts: "A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.". The static cast performs lvalue-rvalue conversion immediately, so in C++0x it's not "used".]

[编辑:gcc正在应用来自c++ 0x草稿的规则:“一个变量或非重载函数,其名称作为一个潜在的评估表达式,除非它是一个满足在常量表达式(5.19)中出现的需求的对象,并且立即应用lvalue-to-rvalue转换(4.1)。”静态转换立即执行lvalue-rvalue转换,所以在c++ 0x中,它不是“使用”的。

The practical problem with the const reference is that foo is within its rights to take the address of its argument, and compare it for example with the address of the argument from another call, stored in a global. Since a static data member is a unique object, this means if you call foo(kConst) from two different TUs, then the address of the object passed must be the same in each case. AFAIK GCC can't arrange that unless the object is defined in one (and only one) TU.

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

OK, so in this case foo is a template, hence the definition is visible in all TUs, so perhaps the compiler could in theory rule out the risk that it does anything with the address. But in general you certainly shouldn't be taking addresses of or references to non-existent objects ;-)

在这个例子中,foo是一个模板,因此在所有的图中都可以看到定义,所以编译器在理论上可以排除它对地址做任何事情的风险。但一般来说,你当然不应该对不存在的对象进行地址或引用;

#2


18  

If you're writing static const variable with initializer inside class declaration it's just like as if you've written

如果在类声明中使用初始化器来编写静态const变量,就像写了一样。

class Bar
{
      enum { kConst = 1 };
}

and GCC will treat it the same way, meaning that it does not have an address.

GCC会以同样的方式对待它,也就是说它没有地址。

The correct code should be

正确的代码应该是。

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

#3


9  

This is a really valid case. Especially because foo could be a function from the STL like std::count which takes a const T& as its third argument.

这是一个非常有效的例子。特别是因为foo可以是STL的函数,比如std::count,它以const t&as为第三个参数。

I spent much time trying to understand why the linker had problems with such a basic code.

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

The error message

错误消息

Undefined reference to 'Bar::kConst'

未定义的引用“酒吧::kConst”

tells us that the linker cannot find a symbol.

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

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

We can see from the 'U' that Bar::kConst is undefined. Hence, when the linker tries to do its job, it has to find the symbol. But you only declare kConst and don't define it.

我们从“U”中可以看到Bar::kConst是未定义的。因此,当链接器试图完成它的工作时,它必须找到符号。但是你只需要声明kConst,而不要定义它。

The solution in C++ is also to define it as follows:

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();
}

Then, you can see that the compiler will put the definition in the generated object file:

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

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

Now, you can see the 'R' saying that it is defined in the data section.

现在,您可以看到“R”表示它在数据部分中定义。

#4


2  

g++ version 4.3.4 accepts this code (see this link). But g++ version 4.4.0 rejects it.

g++版本4.3.4接受此代码(见此链接)。但是g++版本4.4.0拒绝了它。

#5


1  

I think this artefact of C++ means that any time that Bar::kConst is referred to, its literal value is used instead.

我认为c++的这个artefact意味着任何时候该Bar::kConst被引用,它的文字值被使用。

This means that in practise there is no variable to make a reference point to.

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

You may have to do this:

你可能需要这样做:

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

#6


1  

Simple trick: use + before the kConst passed down the function. This will prevent the constant from being taken a reference from, and this way the code will not generate a linker request to the constant object, but it will go on with the compiler-time constant value instead.

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

#7


1  

You can also replace it by a constexpr member function:

您也可以用constexpr成员函数替换它:

class Bar
{
  static constexpr int kConst() { return 1; };
};

#1


53  

It's intentional, 9.4.2/4 says:

这是有意的,9.4.2/4说:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19) In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program

如果一个静态数据成员是const积分或const枚举类型,它在类定义中的声明可以指定一个常量初始化器,在这种情况下,它应该是一个完整的常量表达式(5.19),成员可以出现在积分常数表达式中。如果该成员在程序中使用,则仍然在名称空间范围内定义。

When you pass the static data member by const reference, you "use" it, 3.2/2:

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

An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19), is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.

表达式是有潜在价值的,除非它出现在需要积分常数表达式的地方(见5.19),它是sizeof运算符(5.3.3)的操作数,或者是typeid运算符的操作数,表达式没有指定多态类类型的lvalue(5.2.8)。如果一个对象或非重载函数的名称出现在一个潜在的表达式中,则使用它。

So in fact, you "use" it when you pass it by value too, or in a static_cast. It's just that GCC has let you off the hook in one case but not the other.

所以实际上,当你通过值传递它时,或者在static_cast中“使用”它。只是海湾合作委员会让你摆脱了困境,而不是另一个。

[Edit: gcc is applying the rules from C++0x drafts: "A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.". The static cast performs lvalue-rvalue conversion immediately, so in C++0x it's not "used".]

[编辑:gcc正在应用来自c++ 0x草稿的规则:“一个变量或非重载函数,其名称作为一个潜在的评估表达式,除非它是一个满足在常量表达式(5.19)中出现的需求的对象,并且立即应用lvalue-to-rvalue转换(4.1)。”静态转换立即执行lvalue-rvalue转换,所以在c++ 0x中,它不是“使用”的。

The practical problem with the const reference is that foo is within its rights to take the address of its argument, and compare it for example with the address of the argument from another call, stored in a global. Since a static data member is a unique object, this means if you call foo(kConst) from two different TUs, then the address of the object passed must be the same in each case. AFAIK GCC can't arrange that unless the object is defined in one (and only one) TU.

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

OK, so in this case foo is a template, hence the definition is visible in all TUs, so perhaps the compiler could in theory rule out the risk that it does anything with the address. But in general you certainly shouldn't be taking addresses of or references to non-existent objects ;-)

在这个例子中,foo是一个模板,因此在所有的图中都可以看到定义,所以编译器在理论上可以排除它对地址做任何事情的风险。但一般来说,你当然不应该对不存在的对象进行地址或引用;

#2


18  

If you're writing static const variable with initializer inside class declaration it's just like as if you've written

如果在类声明中使用初始化器来编写静态const变量,就像写了一样。

class Bar
{
      enum { kConst = 1 };
}

and GCC will treat it the same way, meaning that it does not have an address.

GCC会以同样的方式对待它,也就是说它没有地址。

The correct code should be

正确的代码应该是。

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

#3


9  

This is a really valid case. Especially because foo could be a function from the STL like std::count which takes a const T& as its third argument.

这是一个非常有效的例子。特别是因为foo可以是STL的函数,比如std::count,它以const t&as为第三个参数。

I spent much time trying to understand why the linker had problems with such a basic code.

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

The error message

错误消息

Undefined reference to 'Bar::kConst'

未定义的引用“酒吧::kConst”

tells us that the linker cannot find a symbol.

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

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

We can see from the 'U' that Bar::kConst is undefined. Hence, when the linker tries to do its job, it has to find the symbol. But you only declare kConst and don't define it.

我们从“U”中可以看到Bar::kConst是未定义的。因此,当链接器试图完成它的工作时,它必须找到符号。但是你只需要声明kConst,而不要定义它。

The solution in C++ is also to define it as follows:

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();
}

Then, you can see that the compiler will put the definition in the generated object file:

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

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

Now, you can see the 'R' saying that it is defined in the data section.

现在,您可以看到“R”表示它在数据部分中定义。

#4


2  

g++ version 4.3.4 accepts this code (see this link). But g++ version 4.4.0 rejects it.

g++版本4.3.4接受此代码(见此链接)。但是g++版本4.4.0拒绝了它。

#5


1  

I think this artefact of C++ means that any time that Bar::kConst is referred to, its literal value is used instead.

我认为c++的这个artefact意味着任何时候该Bar::kConst被引用,它的文字值被使用。

This means that in practise there is no variable to make a reference point to.

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

You may have to do this:

你可能需要这样做:

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

#6


1  

Simple trick: use + before the kConst passed down the function. This will prevent the constant from being taken a reference from, and this way the code will not generate a linker request to the constant object, but it will go on with the compiler-time constant value instead.

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

#7


1  

You can also replace it by a constexpr member function:

您也可以用constexpr成员函数替换它:

class Bar
{
  static constexpr int kConst() { return 1; };
};