这是我的代码,a.cpp
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
不发出错误。
我的问题是,
- 谁对上面的代码?叮当声还是g++
- 为什么bar2是错误的,而bar1在clang中是正确的
编译器版本:g++5.4.0和clang 7.0.0
更新:该问题被标记为另一个问题的重复,但它不是。我知道我可以在类外显式地添加定义,让它通过clang。这个问题是关于为什么g++&叮当声。
您似乎认为,如果一个编译器是对的,那么另一个编译器一定是错的。程序要么包含错误(然后接受它的编译器是错误的(,要么没有(然后拒绝它的编译器就是错误的(。这反过来又依赖于一个隐含的假设,即所讨论的错误,即缺少ODR使用实体的定义,是一个可诊断的错误。不幸的是,事实并非如此。该标准明确规定:
[basic.def.odr/10]每个程序都应包含每个非内联函数或变量的一个定义,该函数或变量是odr在该程序中使用的,而不是丢弃的语句无需诊断。
尽管该标准的这一条款存在问题且不受欢迎,但它确实存在。由于缺少定义,您的程序有未定义的行为,并且不需要实现来诊断它。因此,两个编译器在任何优化级别上都是技术正确的。
在带有强制拷贝省略的C++17中,程序不再包含任何ODR。使用有问题的变量constexpr静态数据成员是隐式内联的,不需要单独的卸载(感谢Oliv(。
回答我自己的问题。
我对odr的使用有一些模糊的理解。
- 对于文字类型Foo::bar1,它没有使用odr,所以它很好
- 对于结构体Foo::bar2:当返回函数体内部的结构体时,它将调用其复制构造函数,该构造函数引用
Foo::bar2
。所以Foo::bar2
是odr使用的,它的定义必须存在于程序中的某个位置,否则会导致链接错误
但是g++为什么不抱怨呢?我想这与编译器优化有关。
验证我的猜测:
-
复制省略
添加-fno elide构造函数,
g++ -fno-elide-constructors -std=c++11 a.cpp
/tmp/ccg1z4V9.o:在函数
foo2()': a.cpp:(.text+0x27): undefined reference to
Foo::bar2'中所以,是的,复制省略会影响这一点。但
g++ -O1
仍然通过。 -
函数内联
添加-fno行,
g++ -O1 -fno-elide-constructors -fno-inline -std=c++11 a.cpp
/tmp/ccH8dguG.o:在函数
foo2()': a.cpp:(.text+0x4f): undefined reference to
Foo::bar2'中
结论既是复制省略;函数内联将影响其行为。g++&clang是因为g++默认启用了复制省略,但clang没有。