链接器找不到符号但它们确实存在?

7

正在尝试编译这个cfgparser示例

$ g++ example.cc -lcfgparser
: In function `main':
example.cc:(.text+0x6b): undefined reference to `ConfigParser_t::readFile(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
example.cc:(.text+0x160): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) const'
example.cc:(.text+0x2d9): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int*) const'
example.cc:(.text+0x43c): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double*) const'
example.cc:(.text+0x5b1): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool*) const'
example.cc:(.text+0x78c): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*) const'
example.cc:(.text+0xa15): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) const'
example.cc:(.text+0xba2): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) const'
example.cc:(.text+0xd15): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) const'
example.cc:(.text+0xe7f): undefined reference to `ConfigParser_t::getValue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*) const'
collect2: error: ld returned 1 exit statu

但很明显那些符号已经存在:

$ nm -gC /usr/lib/libcfgparser.so
000000000003710 T ConfigParser_t::readFile(std::string const&)
0000000000004880 T ConfigParser_t::ConfigParser_t(std::string const&)
00000000000024c0 T ConfigParser_t::ConfigParser_t()
0000000000004ad0 T ConfigParser_t::ConfigParser_t(std::string const&)
00000000000024a0 T ConfigParser_t::ConfigParser_t()
0000000000004d20 T ConfigParser_t::getOptions(std::string const&) const
00000000000028d0 T ConfigParser_t::getSections() const
0000000000002ff0 T ConfigParser_t::getValue(std::string, std::string, bool*) const
0000000000002de0 T ConfigParser_t::getValue(std::string, std::string, double*) const
0000000000002bd0 T ConfigParser_t::getValue(std::string, std::string, int*) const
00000000000027d0 T ConfigParser_t::getValue(std::string, std::string, std::string*) const
0000000000003500 T ConfigParser_t::getValue(std::string, std::string, std::vector<std::string, std::allocator<std::string> >*) const

与SO上的其他类似问题不同,这个问题与链接顺序无关,因为只有一个目标文件在链接。我是否遗漏了什么?


1
我的猜测是,这两个模块的构建方式不同,导致它们对 std::string 的含义也不同。可能是链接了不同版本的 C++ 运行库? - Igor Tandetnik
问题可能是您包含了 stdio.h 而不是 cstdio,如果您复制了示例代码。这将导致 char 相关的链接错误。其他需要检查的事项:您的 libcfgparser.so 是否使用支持 wchar_tstd::string 构建? - starturtle
我曾经遇到过同样的问题,确保ABI兼容性,确保-m32/-m64与您的库/可执行文件构建匹配。 - ceorron
1
我只是想说谢谢你分享了用于 nm 的命令行。我以前从未听说过这个工具,而找到这种诊断问题的方法(对我来说是命名空间问题)为我节省了数小时的时间。谢谢! - Daniel Peirano
1个回答

14

但很明显这些符号是存在的:

显然不是这样的。仔细看一下,你会发现在链接器报告为未定义的签名和从库中报告的签名之间没有匹配项。

标准头文件<string>std::string定义为以下typedef:

std::basic_string<char, std::char_traits<char>, std::allocator<char>>

截至cxx11 ABI(GCC 4.7.0)时,<string>的GCC实现将std::string定义为以下类型的typedef:

std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>

std::__cxx11名称空间包含那些二进制实现符合cxx11 ABI的类型。

这意味着,如果一个包含标准头文件<string>的C++翻译单元在GCC >= 4.7下编译成目标代码,其中不会包含一个可解析为std::string的符号。

如果一个二进制文件(例如你的libcfgparser.so)包含了一个可解析为std::string的符号,那么无论它在构建它的源代码中可能引用什么,它都不是std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>,并且不能与使用该定义的std::string编译的其他二进制文件链接。

你的libcfgparser.so是由cfgparser Sourceforce项目安装的libcfgparser0_1.1.2_amd64.deb中的一个。我猜这是因为我在链接同样的example.cc程序和该包安装的库时遇到了链接错误。

这个错误的解释如下:

/usr/lib$ strings -a libcfgparser.so | grep "GCC: ("
GCC: (Debian 4.3.2-1.1) 4.3.2
...

这告诉我们这个库是使用GCC 4.3.2构建的,属于cxx11 ABI之前的版本。

在您的nm输出中出现的std::string是一个cxx11之前的缩略语, 代表std::basic_string<char,std::char_traits<char>,std::allocator<char>>

这个libcfgparser.so与您当前的GCC不兼容,因此您不能将其与使用该编译器构建的程序链接起来。您可以从源包libcfgparser-1.1.2.tar.bz2使用当前的编译器构建libcfgparser,然后将您自己构建的库与程序链接。

这会有所帮助,但不是一件轻松的事情。首先,您需要修复源包的损坏和过时的autotooling,以创建一个可工作的./configure脚本。

这并没有给包维护者的熟练程度留下很好的印象。此外,版权为2008年的包源代码质量很菜鸟。如果您想要一个解析INI格式文件的C++解析器,那么boost::property_tree::ini_parser将是goto解决方案。请参见此答案


1
谢谢!你的回答非常有帮助和富有教育性。我猜测的原因也类似,但我无法下结论,可能是因为“在你的 nm 输出中出现的 std::stringstd::basic_string<char, std::char_traits<char>, std::allocator<char>> 的一个 pre-cxx11 demangling 缩写 - 我不知道如何正确地将 std::string 还原。” - qweruiop
1
同时使用boost::property_tree::ini_parser是一个不错的选择。 - qweruiop
@qweruiop 别客气。std::string(解码)= Ss(编码)是在早期的cxx11 GCC名字重整中硬编码的缩写。没有逻辑,只是为了方便。但你可以确信的是,在二进制文件中,不论是编码还是解码的符号,只要它们是不同的,对于链接器来说它们就是不同的,因为这是链接器所看到的。 - Mike Kinghan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接