C++类成员函数未定义的引用

3

当我尝试使用librbd时,遇到了以下链接问题。

以下是我的代码片段。

  • main.cc
#include <iostream>
#include <rados/librados.hpp>
#include <rbd/librbd.hpp>

int main(){
    // Initialize and open an rbd image
    std::string pool = "xxx";
    std::string image_name = "xxxx";
    int r;
    librados::Rados cluster;
    librados::IoCtx io_ctx;
    librbd::Image image;
    librbd::RBD rbd;
    r = cluster.init("cinder-ctest");
    r = cluster.connect();
    r = cluster.ioctx_create(pool.c_str(), io_ctx);
    r = rbd.open_read_only(io_ctx, image, image_name.c_str(), NULL);

    std::string id;
    image.get_id(&id);   // <- Where the problem occurs
    std::cerr << id << std::endl;
    return 0;
}

使用以下命令编译时出现错误。
$ g++ main.cc -o info -lrbd -lrados 
/tmp/ccOpSFrv.o: In function `main':
main.cc:(.text+0x12b): undefined reference to `librbd::Image::get_id(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*)'
collect2: error: ld returned 1 exit status

但是我使用 nm 命令查看 get_id 是否存在:

$ nm -D /usr/lib64/librbd.so | grep get_id
0000000000083d00 T rbd_get_id
000000000008de10 T _ZN6librbd5Image6get_idEPSs
                 U _ZN8librados7v14_2_05IoCtx6get_idEv

并且它在全球范围内可见:

$ readelf -s /usr/lib64/librbd.so | grep get_id
   498: 0000000000083d00    70 FUNC    GLOBAL DEFAULT   11 rbd_get_id
   559: 000000000008de10    54 FUNC    GLOBAL DEFAULT   11 _ZN6librbd5Image6get_idEP

为什么编译时会出现错误:librbd::Image::get_id 未定义的引用?这个函数显然是存在的,让我感到疑惑。


执行 g++ -c main.cc 命令,并检查 nm main.o 命令输出的未定义符号 get_id。如果它有一个不同的名称,那么你遇到了ABI不兼容的问题。 - aschepler
@aschepler 我认为你是对的,如何解决这种问题?$ nm main.o | grep get_id U _ZN6librbd5Image6get_idEPNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE - toliu
这个链接包含了整个解释以及一些解决方法的想法:https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html - John Drouhard
2个回答

5
一些背景:C++11通过向几个成员函数添加noexcept说明符略微改变了std::string接口,但事实证明这个轻微的改变意味着libstdc++必须以不兼容ABI的方式重新编写他们的std::string实现。为了向后兼容,他们保留了旧版本,并将新版本放在内联命名空间中,因此在链接器看来它被命名为std::__cxx11::basic_string。有关更多信息,请参见this page

这就是你遇到麻烦的地方。_ZN6librbd5Image6get_idEPSs解缠后变成:

librbd::Image::get_id(
    std::basic_string<
        char,
        std::char_traits<char>,
        std::allocator<char>
    >*
)

那个函数接受旧版本的std::string,但你传递给它的是指向新版本std::string的指针。可能你所拥有的librbd版本是使用旧版GCC构建的,或者是故意针对旧ABI进行构建的。
你有几个解决方案可供选择:
  1. 查找已经为libstdc++的新ABI构建的librbd版本。
    • 如果你使用的版本来自于你发行版的包管理器,你可能需要查找其他(例如Conan或vcpkg)。
  2. 针对新ABI自己构建librbd。
    • 我不太熟悉这个库,所以不知道这样做有多难。在某些发行版中,他们的工具链会阻止这样做。
  3. 针对旧ABI构建你的应用程序。
    • 如上面链接的页面所说,你可以定义_GLIBCXX_USE_CXX11_ABI预处理器宏为0,告诉libstdc ++使用旧版本的std :: string实现。它在技术上并不完全遵守C ++11及以后的标准修订,但基本相同。

0

看起来您使用的是兼容C++11的编译器,生成了_ZN6librbd5Image6get_idEPNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE这样的名称,但您的库是在C++03、C++98或类似于C++11之前的版本中编译的。

如果您可以访问库源代码,请使用C++11重新编译它;如果您无法访问源代码,请以C++03兼容模式编译程序。


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