【问题标题】:Template static member definition depends on order passed to linker模板静态成员定义取决于传递给链接器的顺序
【发布时间】:2020-04-15 14:04:38
【问题描述】:

下面的代码,有2个模板静态字段成员的定义,每个定义定义template1<int>::x具有不同的值。

人们会期望链接器拒绝此类重新定义,因为它们具有不同的值。

但是 g++ 和 MSVC 的编译和链接传递,以及使用哪个定义取决于源传递给链接器的顺序。

此行为是否符合 C++ 标准、未定义行为或链接器错误?

my_template.h

template <class T>
class template1
{
public:
    static int x;
};

Src2.cpp

#include <stdio.h>
#include "my_template.h"

template <class T>
int template1<T>::x = 2; 

void my_func() // definition
{
    printf("my_func: template1<int>::x = %d\n", template1<int>::x); // definition of X to 2.
    printf("my_func: template1<char>::x = %d\n", template1<char>::x); // definition of X to 2.
}

Main.cpp

#include <cstdio>
#include "my_template.h"

template <class T>
int template1<T>::x = 1;

void my_func();

int main()
{
    printf("main: template1<int>::x = %d\n", template1<int>::x); // definition of X to 1.
    my_func();
    return 0;
}

使用 g++ (MinGW.org GCC Build-20200227-1) 9.2.0+ 编译

编译1

g++ -o prog Src2.cpp Main.cpp

输出1

main: template1<int>::x = 2
my_func: template1<int>::x = 2
my_func: template1<char>::x = 2

编译2

g++ -o prog Main.cpp Src2.cpp

输出2

main: template1<int>::x = 1
my_func: template1<int>::x = 1
my_func: template1<char>::x = 2

也观察到

Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28612 for x86

当我反汇编带有-S标志的代码时,每个编译单元都定义了相同的符号名称。

Nightra合作。

【问题讨论】:

  • FWIW,如果你有 C++17,你可以在类中使用static inline int x = 2;,甚至不必担心在类外定义成员。在 C++17 之前,由于定义是一个模板,您可以将其移动到类之后的头文件中,因为所有模板都是隐式内联的。

标签: c++ templates linker g++


【解决方案1】:

这违反了ODR(要求实体必须完全有一个定义,如果使用的话)。所以程序有UB。

编译器无法诊断这一点,因为每个翻译单元都很好。理论上,链接器可以诊断这一点,但实际上它不会那样做。

【讨论】:

  • 这种情况只发生在模板中,使用全局变量,不传递链接。
  • 也许是这样,但它仍然是 UB。你的问题是,“为什么链接器诊断一个,而不是另一个?”
【解决方案2】:

此行为是否符合 C++ 标准、未定义行为或链接器错误?

这是未定义的行为 (UB)。


来自N4659[basic.def.odr]/4 [强调我的]:

每个程序都应包含每个程序的确切定义 非内联函数或在该程序中使用 odr 的变量 在废弃语句之外; 无需诊断。这 定义可以显式出现在程序中,可以在 标准或用户定义的库,或(在适当时)它是 隐式定义(参见 [class.ctor]、[class.dtor] 和 [class.copy])。 每次翻译都应定义一个内联函数或变量 在废弃语句之外使用它的单位。

constexprstatic模板的成员变量不是隐含的inline,因此这是UB,不需要诊断。

我们也可以求助于[basic.def.odr]/6 以获得更强有力的声明(甚至不需要使用 ODR)[引用选定的摘录,强调我的]:

可以有多个 [...] 静态数据成员的定义 程序中的类模板 [...],前提是每个定义 出现在不同的翻译单元中,并且提供 定义满足以下要求。给定这样一个实体 命名为D 在多个翻译单元中定义,则

  • D 的每个定义应由相同的令牌序列组成;和

[...]

如果D 的定义满足所有这些要求,那么 行为就好像有一个 D 的定义。如果 D 的定义不满足这些要求,然后 行为未定义

D 的两个不同定义(在您的情况下,template1&lt;int&gt;::xD 的每个定义都应包含相同的令牌序列”没有满足,因此我们自然不可能满足 "[...] 就好像有一个单一的定义 D";因此UB。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-20
    • 2016-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多