访问静态constexpr成员时,c++链接器错误和未定义引用


#include <stddef.h>
#include <array>
struct S {
static constexpr std::array<int,5> sca = {1,2,3,4,5};
static constexpr int foo(size_t i) {
return sca[i];
}
};
int main(int argc, char **argv) {
S s;
return s.foo(4);
}

编译这个给了我一个链接器错误和一个undefined reference:

$ g++ --std=c++14 -O0 -o test1 test1.cpp 
/usr/bin/ld: /tmp/ccfZJHBY.o: warning: relocation against `_ZN1S3scaE' in read-only section `.text._ZN1S3fooEm[_ZN1S3fooEm]'
/usr/bin/ld: /tmp/ccfZJHBY.o: in function `S::foo(unsigned long)':
test1.cpp:(.text._ZN1S3fooEm[_ZN1S3fooEm]+0x1a): undefined reference to `S::sca'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

我检查了我的g++版本,结果是11.3:

$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
...

最奇怪的是:这段代码在x86-64 gcc 11.3的CompilerExplorer中编译得很好:https://godbolt.org/z/rjG31z9hY

所以我认为这可能是一个编译器版本问题,但用g++-12编译产生了相同的结果:

$ /usr/bin/g++-12 --std=c++14 -O0 -o test1 test1.cpp
/usr/bin/ld: /tmp/ccH1PFkh.o: warning: relocation against `_ZN1S3scaE' in read-only section `.text._ZN1S3fooEm[_ZN1S3fooEm]'
/usr/bin/ld: /tmp/ccH1PFkh.o: in function `S::foo(unsigned long)':
test1.cpp:(.text._ZN1S3fooEm[_ZN1S3fooEm]+0x1a): undefined reference to `S::sca'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
$ /usr/bin/g++-12 --version
g++-12 (Ubuntu 12.1.0-2ubuntu1~22.04) 12.1.0
...

我发现它与--std=c++17-O1一起工作:

$ g++ --std=c++14 -O1 -o test1 test1.cpp 
$ ./test1 
$ echo $?
5
$ g++ --std=c++17 -O0 -o test1 test1.cpp 
$ ./test1 
$ echo $?
5

我还发现将foo的结果绑定到堆栈上的变量以某种方式解决了这个问题:

int main(int argc, char **argv) {
S s;
constexpr int x = s.foo(4);
return x;
}

并允许在不改变编译标志的情况下构建和执行此代码:

$ g++ --std=c++14 -O0 -o test2 test2.cpp 
$ ./test2 
$ echo $?
5

为什么会这样?

不管你的哪一个变化,我们看sca[i]foo或使用sca。(您甚至不需要调用foo来实现这种情况。)因此,必须有sca的定义。但是,如果没有可用的,则程序是病态的,不需要诊断(IFNDR),这意味着编译器可以对问题发出诊断,但不必这样做。

实际上,这只是一个编译器是否通过推断您将从中读取什么值而不是实际调用foo来优化对sca的访问的问题。由于foo也是隐式的inline,编译器不必为它发出定义(但有些编译器会这样做),因此在目标文件中是否会有对sca的引用供链接器解析将取决于优化和编译器/链接器的具体情况。

在c++ 17之前,static constexpr std::array<int,5> sca = {1,2,3,4,5};不是sca的定义,所以你的程序是IFNDR。您尝试的所有编译器都运行正常。对此不需要任何警告或错误。必须在类定义之后添加定义std::array<int,5> S::sca;,以使程序结构良好。

由于c++ 17static constexpr std::array<int,5> sca = {1,2,3,4,5};是一个定义(因为constexpr使它隐式地成为inline),并且程序在任何变体中都是格式良好的。同样,编译器的行为是正确的。在c++ 17之前,根本没有inline静态数据成员,所以constexpr不能暗示它。

我在这个答案中找到了一部分答案。

但是为什么它在CompilerExplorer中工作对我来说仍然是一个谜,以及为什么将它绑定到堆栈上的变量可以解决这个问题。

编辑1:CompilerExplorer

正如273K在他的评论中所写的,我忘记检查编译器输出选项"Link to binary"。

最新更新