【问题标题】:undefined reference to class static constexpr struct, g++ vs clang对类静态 constexpr 结构的未定义引用,g++ vs clang
【发布时间】:2018-11-21 07:46:51
【问题描述】:

这是我的代码,a.cp​​p

struct int2
{
    int x, y;
};
struct Foo{
    static constexpr int bar1 = 1;
    static constexpr int2 bar2 = {1, 2};
};
int foo1(){
    return Foo::bar1; // this is ok for both clang++ and g++
}
int2 foo2(){
    return Foo::bar2; // undefined reference to `Foo::bar2' in clang++
}
int main(){ std::cout << foo2().x << std::endl; return 0; }

使用clang编译,clang++ -std=c++11 a.cpp

/tmp/a-0dba90.o: In function `foo2()':
a.cpp:(.text+0x18): undefined reference to `Foo::bar2'
clang-7: error: linker command failed with exit code 1 (use -v to see 
invocation)

g++ -std=c++11 a.cpp 不会发出任何错误。

我的问题是,

  1. 谁是正确的上述代码? clang 还是 g++?
  2. 为什么在 clang 中 bar2 是错误的而 bar1 是正确的?

编译器版本:g++ 5.4.0 和 clang 7.0.0

更新:该问题被标记为与another question 重复,但事实并非如此。我知道我可以在类之外添加显式定义以使其通过 clang。这个问题是关于为什么 g++&clang 之间的所有区别。

【问题讨论】:

  • int2是什么类型?它不在标准中。
  • @arorias 是的,我包含了定义。
  • @xdot: 哪个版本的clang?从 clang 3.0 开始无法在 Godbolt 上复制。 godbolt.org/z/UwjgHv
  • 对我来说看起来像是一个 clang 错误,除非标准在 C++11 中被打破。
  • 如果编译器根据优化级别发出错误或不发出错误,则该编译器存在错误您的程序存在 UB。

标签: c++ c++11 one-definition-rule


【解决方案1】:

您似乎认为如果一个编译器是正确的,那么另一个编译器一定是错误的。程序要么包含错误(然后接受它的编译器是错误的),要么不包含错误(然后拒绝它的编译器是错误的)。这又依赖于一个隐含的假设,即所讨论的错误,即缺少对使用 ODR 的实体的定义,是一个可诊断的错误。不幸的是,事实并非如此。该标准明确指出:

[basic.def.odr/10] 每个程序都应包含一个定义,即在该程序中被丢弃的语句之外的每个非内联函数或变量的定义; 无需诊断

由于标准的这一规定存在问题和不受欢迎,它就在那里。由于缺少定义,您的程序具有未定义的行为,并且不需要实现来诊断它。所以这两个编译器在任何优化级别在技术上都是正确的。

在具有强制复制省略的 C++17 中,程序不再包含任何 ODR 使用所讨论的变量 constexpr 静态数据成员是隐式内联的,不需要单独的定义(感谢 Oliv)。

【讨论】:

  • 在 C++17 中,constexpr 静态成员变量是 inline 的,所以无论是否复制省略,程序在 C++17 中的格式都是正确的。无论这里没有复制省略。 GCC 和 Clang 之间的区别在于 GCC 从不考虑琐碎的复制构造函数 odr 使用源变量(这是语言的扩展)。可以通过将main的正文替换为int2{Foo::bar2}来验证。
【解决方案2】:

回答我自己的问题。

我对@9​​87654321@ 有一些模糊的了解。

  • 对于文字类型 Foo::bar1,它不是 odr-used,所以没问题。
  • 对于 struct Foo::bar2:当在函数体内返回一个结构时,它将调用其复制构造函数,该构造函数引用 Foo::bar2。所以Foo::bar2是odr-used,它的定义必须存在于程序的某个地方,否则会导致链接错误。

但是为什么 g++ 不抱怨呢?我猜这与编译器优化有关。

验证我的猜测:

  1. 复制省略

    添加-fno-elide-constructors,g++ -fno-elide-constructors -std=c++11 a.cpp

    /tmp/ccg1z4V9.o:在函数foo2()': a.cpp:(.text+0x27): undefined reference toFoo::bar2'

    所以,是的,复制省略会影响这一点。 但是g++ -O1 还是通过了。

  2. 函数内联

    添加-fno-line,g++ -O1 -fno-elide-constructors -fno-inline -std=c++11 a.cpp

    /tmp/ccH8dguG.o: 在函数foo2()': a.cpp:(.text+0x4f): undefined reference toFoo::bar2'

结论是复制省略和函数内联都会影响其行为。 g++ 和 clang 的区别在于 g++ 默认启用了复制省略,而 clang 没有。

【讨论】:

  • @geza 在 foo2() 中创建临时变量时编译得很好:int2 foo2(){ auto tmp = Foo::bar2; return tmp; },所以可能是在函数返回语句中调用 odr 使用的 clang 错误?
  • @phön:由于 odr-use 违规是 NDR,当代码成功编译时,我们无法得出结论。对于您的示例,可能 clang 省略了副本,这就是它编译的原因。但是,int2 odr-uses Foo::bar2 的复制构造函数(我在这里考虑 pre-C++17)。这就是我现在的想法:第一个示例编译,因为它确实不使用 odr-use Foo::bar1 (stackoverflow.com/questions/53429108/…)。但是Foo::bar2 在 C++17 之前是 odr 使用的。但是如果编译器优化掉了copy constr,那么我们就不知道了。
  • @geza 是的,我看到它正在编译并匆忙发表评论。对不起。所以问题是 int2 的副本是否确实引入了 odr 用法。如果是,可以拒绝代码。但由于 int2 非常微不足道,我认为/认为它应该像普通 int 一样处理。您的链接问题将回答我希望
猜你喜欢
  • 2021-11-09
  • 2016-01-25
  • 2015-08-30
  • 1970-01-01
相关资源
最近更新 更多