error: undefined symbol: std::__cxx11::basic_string和std::__1::basic_string

问题报错

最近Linux上编译一个项目遇到这样的报错:

ld.lld: error: undefined symbol: std::__cxx11::basic_string, std::allocator >::compare(char const*) const
>>> referenced by xxx.cc
>>>               xxx.o:(tflite::FlatBufferModel::GetMinimumRuntime[abi:cxx11]() const) in archive /home/xxx/project/libtensorflow-lite.a

ld.lld: error: undefined symbol: std::__cxx11::basic_string, std::allocator >::_M_create(unsigned long&, unsigned long)
>>> referenced by xxx.cc
>>>               xxx.o:(tflite::FlatBufferModel::GetMinimumRuntime[abi:cxx11]() const) in archive /home/xxx/project/libtensorflow-lite.a

还有一大堆类似这个:

undefined symbol: cv::imwrite(cv::String const&, cv::_InputArray const&, std::__1::vector > const&)

一个报错来源tflite静态库,另一个来源是opencv_world动态库。
其实不只是这两个库,编译项目时用到其他第三方库的时候也很有可能遇到这种报错

先说结论

  1. 解决方案1:在编译参数里加上:-D_GLIBCXX_USE_CXX11_ABI=0,如果不行把0改成1,如果还不行,看2:。
  2. 解决方案2:在编译参数里指定:-std=c++11 -stdlib=libstdc++,如果不行,在编译参数里指定:-std=c++11 -stdlib=libc++ -lc++ -lc++abi

注意要清除掉之前的编译产物.o .a .so .h.gch等重新编译。

下面是问题分析:


出错原因

像这种std__1std::__cxx11报错报到非常底层的数据结构上,问题一般都出在链接上,像这里这两个报错,最本质的原因是:第三方库和你的项目用了不同的方式进行编译,具体是什么地方不同造成的?我已知的,有这样两个:

  1. 采用的不同版本的C++标准,比如项目编译采用的c++11,但是第三方库,采用的c++03编译而成
  2. 编译采用的C库不同,如第三方库连接的是libc++.so,然而项目编译使用的libstdc++.so

解决方案

第一个、因为编译时采用C++标准不同导致报错

这个原因导致的问题,解决方案在搜索引擎里是烂大街的,对着报错随便一搜都有,都一模一样,而且都标了原创也不写参考链接。个人认为,灵感应该都是源于stackoverflow上的这个讨论:Converting std::__cxx11::string to std::string

在C++03中,基础字符串的定义是:std::basic_string
然而在C++11中,基础字符串变成了:std::__cxx11::basic_string(libstdc++)或者std::__1::basic_string(libc++)。因此编译的时候就会报错,未定义的引用什么的。

那么解决呢,也很简单。如果你的第三方库是自己编译的,那么请重新用和你项目一致的编译参数重新编译一次。尤其是-std=c++11

如果你的第三方库不是自己编译的,那么你就需要迫使自己的项目编译和第三方库保持一致,在你的源文件中加上参数:#define _GLIBCXX_USE_CXX11_ABI 0 0代表关闭C++11特性,1代表开启。出现上面的报错一般都是第三方库没有开启导致的,所以本地也关掉。

如果改源文件不方便,也可以在编译参数中加上:-D_GLIBCXX_USE_CXX11_ABI=0 -D就是宏定义,这样也可以。

如果是C++标准不同导致的问题,上述操作会有用,就算不能完全解决问题,至少报错会变。但是我本次遇到的这个问题,按照上面这办法没用,后来经过分析,才发现了下面这种:

第二个、因为编译时采用的C库不同导致报错

很多人会怀疑这是编译器不同导致的,比如第三方库用gcc编译,而项目用clang编译,这样的话就会报出上面的问题。其实不全对、根本的原因不在编译器,而是他们采用的C库不同,gcc默认用的是libstdc++,而clang默认采用的是libc++。如果在编译时通过-stdlib明确指定一致,编译器一致与否是没关系的。

在上面也提到了,在不同的C库当中,对于基础字符串的定义是有差异的:

  • std::__cxx11::basic_string(libstdc++)
  • std::__1::basic_string(libc++)

那么如果第三方库用gcc编译,不指定的话默认用的就是libstdc++,动态链接的时候,会记录一个名为std::__cxx11::basic_string的符号。而这时候你变编译自己的项目,使用libc++,它提供的是std::__1::basic_string,那连接到第三方库,第三方库去找std::__cxx11::basic_string这个符号,当然会找不到,报错未定义的引用。

解决方案呢,就是编译时明确指定编译时所采用的C库保持一致,重新编译即可

  • 使用libstdc++的话:加上-std=c++11 -stdlib=libstdc++
  • 使用libc++的话:加上-std=c++11 -stdlib=libc++ -lc++ -lc++abi

要么是重新编译自己的项目,要么重新编译第三方库。

我一般推荐第三方库自己编,修改编译参数和自己的项目保持一致。因为第三方库毕竟是历经检验的成熟开源项目,切换C库编译器平台什么的基本问题不大,我们自己的项目,一般很难保证有这样的高水准,换个C库啥乱七八糟错的语法错误都出来了。

方法探究

未定义的引用的问题在C++的编译过程中最为常见,这个错误出现在编译的链接阶段,有九成的原因都是链接库不对导致。遇到这种问题,分析问题的本质离不开基础的编译原理,对于编译参数的基础可以参考:g++编译详解

在此呢就上面的问题进行分析,它出现了对某个函数或者数据结构未定义的引用,比如这个:

 error: undefined symbol: std::__cxx11::basic_string, std::allocator >::compare(char const*) const

找不到一个名为compare的函数,返回值为basic_string。这个函数在我们的代码中没有显示调用,而是字符串在用==进行比较时隐式调用,所以它的报错非常常见因为我们经常会比较字符串的异同。

这时候要做的就是两件事:
1、第三方库中记录的符号原型是什么?
2、我们编译时提供的符号原型是什么?

最上面的报错,可以看得出来一个是在静态库:libtensorflow-lite.a中,一个是在动态库libopencv_world.so中。而且一看函数名字,明显不是我们自己写的,那么我们找找这个库中记录的符号名字是什么:
nm libtensorflow-lite.a | c++filt | grep compare在这里插入图片描述nm -D libopencv_world.so.3.4.6 | c++filt | grep compare
在这里插入图片描述
你看,一个是__cxx11命名空间 ,一个是__1命名空间。这两种放到一个项目里编译不打架才怪呢,当然这里的符号前面的标志都是U,也就是未定义,说明这个符号实在其他动态库里定义的。我们再看看标准库里的定义:
nm -D /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | c++filt | grep compare
error: undefined symbol: std::__cxx11::basic_string和std::__1::basic_string_第1张图片
nm -D /usr/lib/x86_64-linux-gnu/libc++.so.1 | c++filt | grep compare
在这里插入图片描述
看到了吧:两种标准库里对应的命名空间的名字是不一样的,可以看的出来,我这里的libopencv_world.so是使用libc++编译的,而libtensorflow-lite.a是使用libstdc++进行编译的。这个可以通过编译参数-stdlib来指定,像我的项目这种情况下,tensorflow和opencv横竖得重新编译一个。我自己的项目使用的是clang+libc++。说以重新编译了tensorflow,效果如下:
error: undefined symbol: std::__cxx11::basic_string和std::__1::basic_string_第2张图片
这样就确保了libtensorflow-lite.a链接阶段找符号的时候找libc++库里的符号,就解决了报错的问题。

同样其他情况下出现莫名其妙的未定义的引用的问题,也可以参照此方法呢,先看看自己的库里记录的符号签名是什么,然后看看链接库里的符号签名是什么,如果签名一致,说明没有链接到,检查链接库的路径什么的。如果签名不一致,说明连接的库不对,考虑更换版本解决。

参考链接:

  1. https://stackoverflow.com/questions/24342312/clang-seems-to-use-the-gcc-libraries
  2. https://stackoverflow.com/questions/33394934/converting-std-cxx11string-to-stdstring
  3. https://blog.csdn.net/ufolr/article/details/52669333?utm_source=blogxgwz3
  4. https://blog.csdn.net/n_o_n/article/details/99336919
  5. https://www.cnblogs.com/lukybee/p/11846889.html

你可能感兴趣的:(Qt/C++,c++,编译器,编程语言,报错)