将 C++ 共享库链接到 C 程序时如何避免错误

How to avoid errors when linking C++ shared libs into C programs

我正在使用一个库,该库随通常的 AutoTools 生成 configure && make && make install 程序一起提供。该库包含一个主(共享)库和一些工具,大部分是用 C 语言编写的。

现在我 运行 遇到了一个问题,其中一个工具的构建之一在使用仪器时失败(Score-P 包装编译器调用以发挥其魔力)。

我将范围缩小到以下事实:

libMain 使用 C 文件和 1 个 C++ 文件,C 文件使用 gcc 编译器,C++ 文件使用 g++。该库作为共享库与 g++ 链接。
binTool 仅使用 C 文件,但链接到 libMain。

无需工具即可使用。但是,当使用时,它会在与使用 C++ 功能的 g++ 链接时添加额外的库。然后将 binTool 与 gcc 链接起来 undefined reference to 'operator delete[](void*)'(以及一些类似的)

首先:有人可以向我解释一下,为什么我在链接共享库时必须小心(使用 g++,即使二进制文件仅使用 C 代码)?我的印象是,共享二进制文件的链接已经完成,因此链接不应引入任何新的依赖关系,或者依赖关系已经解决(在这种情况下 libMain 会知道它需要 libc++ 并且已经 referenced/stored/whatever-elf-is-doing)

其次:通过阅读 AutoTools 文档,我发现程序的链接器是根据其源文件选择的。由于 libMain 使用 C++ 文件,因此它与 g++ 链接。 binTool 仅使用 C 文件,因此它与 gcc 链接。但是 binTool 也链接 libMain,它是 C++ 链接的,似乎需要与 g++ 链接。
那么罪魁祸首在哪里呢?是 AutoTools 为 binTool 发出了错误的链接器命令吗?或者 g++ 在链接 libMain 时应该做些不同的事情吗?

供参考:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)

ldd libMain:

linux-vdso.so.1
librt.so.1
libpthread.so.0
libm.so.6 libc.so.6
libgcc_s.so.1
libdl.so.2
libnuma.so.1
libltdl.so.7

解决你的问题的想法是编写一个包装库,它的 header 在 C 中, body 在内部用 C++ 编译器编译,这样它可以调用您的 C++ 库函数。

稍后您可以 link 您的 C++ 库及其 C header 到您的应用程序

正如我评论的那样,您可以 link 共享库(当 构建 该库时)与另一个共享库。参见 this answer for details. Read Drepper's How To Write Shared Libraries 论文。

可能,您应该 re-configure 和 re-compile 以及 re-build 您的 libMain。你想 link 它 明确地 -lstdc++ .

也许将一些 LDFLAGS=-lstdc++LIBES=-lstdc++ 传递给 libMainconfigure 可能会有所帮助。参见 this

顺便说一句,有一些 autoconf-ed 库是用 C++ 编码的,可以从纯 C 程序调用(例如 libgccjit),它们是 link 与 -lstdc++

我找到了一个解决方案(TL&DR 跳到胖 SOLUTION):

情况比我想象的要困难得多。发生的事情是: binTool link 到共享库 libMain,其中 link 到共享库 libExtraCxx

  • binTool 是一个 C 程序,因此 link 使用 gcc
  • libMain 包含一个 C++ 文件,因此 link 使用 g++ 编辑。但它不使用任何 C++ 库功能,因此 linker 从 libMain link 过程中省略了 libstdc++
  • libExtraCxx 是一个 C 库,旨在通过自动包装脚本 linked 到 C 程序中。由于 libMain 使用 g++,它得到 libExtraCxx linked。这个库应该拦截 C++ new/delete 调用,并通过使用 GNU 来做到这一点-wrap linker 命令并在内部定义(手动)new/delete 的错位版本,前缀为 __wrap_,并声明前缀为 [=26 的错位版本=].

通常这是可行的,因为当 C++ linker 将被 binTool 使用时,包装器将发出 -wrap 命令,而 libstdc++ 提供 __real_ 函数。

然而,错误是,libstdc++ 永远不会得到 linked:libExtraCxx 是一个仅挂接到 C++ 函数的 C 库。 libMain 不使用任何 C++ 库函数,binTool 又是一个 C 程序,没有共享库 link 进入其中 libstdc++ link 进入其中。

所以可以指出 2 个问题:

  1. libExtraCxx 应该是一个 C++ 库和 links libstdc++ 但我猜这个 "tricks" 是为了明确避免对 C++ 库的依赖所以它可以被 GNU、Intel 或 Clang C++ 编译器使用,它们可能有不同的标准库。
  2. libMain 应该省略 libstdc++ 而不是 。这通常可以通过将 -Wl,--no-as-needed 传递给 compiler/linker 来完成,如 here.
  3. 所述

由于涉及的工作量,我无法更改 libExtraCxx,但我可以将参数传递给编译器,所以我选择了 2.

然而,简单地做 configure <...> LDFLAGS=-Wl,--no-as-needed 是行不通的。使用了标志,但 libstdc++ 不存在。

我发现罪魁祸首是 libMain 使用的 libtool:libtool 2.4.2 does not pass the flag at the right position 据我所知,这个 bug#12880 还没有修复,所以我正在寻找更多。

解决方案
我找到了一个hack here:只需使用CXX="$CXX -Wl,--no-as-needed"。这基本上让 libtool 认为,该标志是编译器命令的一部分,它不会重新排序,将它留在开头。

供参考:我使用的是 starPU,所以 libMain 实际上是 libstarpu-1.2.so。失败的二进制文件 (binTool) 是 starpu_perfmodel_display。 "fake"-C++-Library 来自 Score-P libscorep_adapter_memory_event_cxx.so.5 我只是更改了名称以简化它们。

对于SCORE-P,解决方案有点复杂,因为不能简单地更改 CXX。所以用 ScoreP 编译 starPU 的完整解决方案是:

SCOREP_WRAPPER=off ~/Downloads/starpu-1.2.3/configure --prefix /usr/local CC=scorep-gcc CXX=scorep-g++ FC=scorep-gfortran --with-mpicc=scorep-mpicc --with-mpifort=scorep-mpif77
并且
make SCOREP_WRAPPER_INSTRUMENTER_FLAGS="--opencl --thread=pthread" SCOREP_WRAPPER_COMPILER_FLAGS="-Wl,--no-as-needed"

说明:SCOREP_WRAPPER_COMPILER_FLAGS 将导致包装器将标志传递给编译器。由于 libTool 使用 scorep 包装器,它甚至看不到这些标志,因此它们被透明地添加。