83

考虑以下场景:

  • 共享库 libA.so ,没有依赖关系。
  • 共享库 libB.so,以 libA.so 作为其依赖项。

我想编译一个与 libB 链接的二进制文件。我应该将二进制文件仅与 libB 链接还是与 libA 链接?

有没有办法只链接直接依赖项,让运行时依赖项中未解析的符号解析?

我担心库 libB 的实现将来可能会发生变化,引入其他依赖项(例如 libC、libD、libE)。我会有问题吗?

换句话说:

  • libA 文件:a.cpp 啊
  • libB 文件:b.cpp bh
  • 主程序文件:main.cpp

当然,b.cpp 包含 ah,main.cpp 包含 bh

编译命令:

g++ -fPIC a.cpp -c
g++ -shared -o libA.so a.o

g++ -fPIC b.cpp -c -I.
g++ -shared -o libB.so b.o -L. -lA

我应该使用以下哪个选项?

g++ main.cpp -o main -I. -L. -lB

或者

g++ main.cpp -o main -I. -L. -lB -lA

我无法使用第一个选项。链接器抱怨库 libA 中未解析的符号。但这对我来说听起来有点奇怪。

非常感谢。

-- 更新评论:

当我链接二进制文件时,链接器将尝试解析 main 和 libB 中的所有符号。但是,libB 具有来自 libA 的未定义符号。这就是链接器抱怨的原因。

这就是为什么我也需要与 libA 链接。但是我找到了一种方法来忽略共享库中未解析的符号。看起来我应该使用以下命令行来做到这一点:

g++ main.cpp -o main -I. -L. -lB -Wl,-unresolved-symbols=ignore-in-shared-libs

看起来仍然可以使用该-rpath选项。但是我需要更好地理解它。

有谁知道使用该-Wl,-unresolved-symbols=ignore-in-shared-libs选项时可能存在的任何陷阱?

-- 更新评论 2:

-rpath不应用于此目的。强制在给定目录中找到库很有用。这种-unresolved-symbol方法看起来好多了。

再次感谢。

4

4 回答 4

44

看起来你已经大部分时间了。你的调查做得很好。让我们看看我是否可以帮助弄清楚它背后的“原因”。

这是链接器正在做的事情。当你链接你的可执行文件(上面的'main')时,它有一些未解析的符号(函数和其他东西)。它将查看后面的库列表,尝试解析未解析的符号。一路上,它发现一些符号是由 libB.so 提供的,所以它注意到它们现在由这个库解析。

但是,它还发现其中一些符号使用了您的可执行文件中尚未解析的其他符号,因此它现在也需要解析这些符号。如果不链接 libA.so,您的应用程序将是不完整的。一旦它与 libA.so 链接,所有符号都被解析并且链接完成。

如您所见,使用-unresolved-symbols-in-shared-libs, 并不能解决问题。它只是推迟它,以便在运行时解析这些符号。这就是-rpath目的:指定要在运行时搜索的库。如果这些符号无法解析,您的应用将无法启动。

找出库依赖关系并不是一件容易的事,因为一个符号可以由多个库提供,并且可以通过链接其中任何一个来满足。

这里对这个过程还有另一种描述:为什么链接库的顺序有时会导致 GCC 出错?

于 2013-05-31T01:58:15.120 回答
23

对于仅具有直接依赖项的动态链接,您可以-Wl,--as-needed以下位置添加库: -Wl,--as-needed

gcc main.c -o main -I. -L. -Wl,--as-needed -lB -lA

要检查直接依赖关系,您应该使用readelf而不是ldd因为 ldd 也显示间接依赖关系。

$ readelf -d main | grep library
0x0000000000000001 (NEEDED)             Shared library: [libB.so]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

ldd 还显示了间接依赖关系:

$ LD_LIBRARY_PATH=. ldd ./main
linux-vdso.so.1 (0x00007fff13717000)
libB.so => ./libB.so (0x00007fb6738ed000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb6734ea000)
libA.so => ./libA.so (0x00007fb6732e8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb673af0000)

如果您使用cmake,您可以添加以下行以仅包含直接依赖项:

set(CMAKE_EXE_LINKER_FLAGS    "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}")
于 2015-03-23T11:10:45.933 回答
1

另一种选择是使用libtool

如果您将g++调用更改libtool --mode=compile g++为编译源代码然后libtool --mode=link g++创建应用程序关闭libB,则将libA自动链接。

于 2016-06-14T14:36:02.393 回答
0

这是一篇有趣的帖子-我也对此感到震惊,但我认为您在这里错过了一点..

思路如下,对吧?

main.cpp =(depends)=> libB.so =(depends)=> libA.so

让我们进一步考虑..

  • 在 a.cpp (并且只有在那里)中,您定义了一个类/变量,我们称它为“symA”
  • 在 b.cpp(并且只有在那里)中,您定义了一个类/变量,我们称它为“symB”。
  • symB 使用 symA
  • main.cpp 使用 symB

现在,libB.so 和 libA.so 已按照您的描述编译。之后,您的第一个选项应该可以工作,即:

g++ main.cpp -o main -I. -L. -lB

我想你的问题源于这样一个事实

在 main.cpp 你也指 symA

我对么?

如果您在代码中使用符号,则必须在 .so 文件中找到该符号

相互引用共享库(即创建 API)的整个想法是隐藏更深层中的符号(想想剥洋葱)并且不使用。.. 即不要在您的 main.cpp 中引用 symA,而只引用 symB(并让 symB 仅引用 symA)。

于 2017-08-15T17:23:20.277 回答