链接到 C++ 库时出现意外的符号解析

Rom*_*n B 5 c++ linker compilation

我正在构建一个基于两个对象(add.o 和 sub.o)的库“libsimplemath.a”。

主要只依赖于 add.o 接口。当代码与 libsimplemath.a 链接时,我希望它可以工作。

在构建 libsimplemath.a 时,我决定将 -std 标志从 c++14 切换到 c++17。在这一点上,由于某些原因 sub.o 也由链接器带来,并且因为它包含未定义的符号链接失败。

要了解为什么 sub.o 被考虑在内,可以查看链接器映射文件:

Archive member included to satisfy reference by file (symbol)

    ./libsimplemath.a(add.o)      main.o (add(int, int))
    ./libsimplemath.a(sub.o)      main.o (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string())
    /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS)
                                  /usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/Scrt1.o (__libc_csu_init)
Run Code Online (Sandbox Code Playgroud)

一些 stl 符号,这里的 ~basic_string() 在 sub.o 中解析。

问题是:即使没有明确使用 sub.o 也会被考虑在内吗?如果是这样,我目前正在做的事情有什么问题?

语境:

$ g++ --version
g++ (Ubuntu 8.3.0-6ubuntu1) 8.3.0
$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.32
Run Code Online (Sandbox Code Playgroud)

添加.cpp

int add(int a, int b) { return a + b; }
Run Code Online (Sandbox Code Playgroud)

子.cpp

#include <string>
void undef_symb(void);

int sub(int a, int b)
{
  std::string s;
  undef_symb();
  return a - b;
}
Run Code Online (Sandbox Code Playgroud)

主程序

#include <string>
int add(int, int);
int main()
{
  std::string s = "Hello";
  return add(0, 0);
}
Run Code Online (Sandbox Code Playgroud)

汇编

g++ -O0 -std=c++17 -o sub.o -c sub.cpp
g++ -O0 -std=c++17 -o add.o -c add.cpp
ar rc libsimplemath.a add.o sub.o
g++ -O0 -std=c++14 -o main.o -c main.cpp
g++ -o main main.o -L. -lsimplemath -Wl,-Map=main.g++.map
/usr/bin/ld: ./libsimplemath.a(sub.o): in function `sub(int, int)':
sub.cpp:(.text+0x2b): undefined reference to `undef_symb()'
collect2: error: ld returned 1 exit status
make: *** [Makefile:8: all] Error 1
Run Code Online (Sandbox Code Playgroud)

n. *_* m. 0

extern template class ...是一个完全正常的 C++ 声明,可以出现在用户代码中,无论选择何种 C++ 标准。您不应该自己实例化标准库中的事物(无论合法与否),但任何用户定义的类模板都会发生同样的事情。您可以不使用标准库和选项来构建几乎相同的示例-std=。只需制作一个模板,添加extern template class ...到一个文件中,不要将其添加到另一文件中。

没有任何标准和 ABI 可以保护您免受此类事件的影响。您始终需要小心链接到程序中的对象。

当然,这在实践中仍然是一个问题,恕我直言,这是 libstdc++ 中的一个错误(它不应该让简单的事情变得困难),但是一旦你知道发生了什么,就有一个简单的解决方法。

编译(使用-std=c++20 -O0)一个包含以下内容的对象:

// for the benefit of C++17 users
// who compile their libraries with -O0
#include <string>
std::string a;
Run Code Online (Sandbox Code Playgroud)

并在链接器有机会看到 之前将其链接到您的程序sub.o。例如,将其放在第一位libsimplemath.a

您仍然可以在 C++17 和 C++20 之间正常互操作。不需要在链接时提供每个依赖项(这将是一件可怕的事情)。