100

我需要将基于 Ubuntu 12.10 和 GCC 4.7 的 libstdc++ 构建的 C++ 应用程序部署到运行 Ubuntu 10.04 的系统上,该系统带有相当旧版本的 libstdc++。

目前,我正在编译-static-libstdc++ -static-libgcc,正如这篇博文所建议的那样:静态链接 libstdc++。作者警告不要在静态编译 libstdc++ 时使用任何动态加载的 C++ 代码,这是我尚未检查的内容。尽管如此,到目前为止一切似乎都很顺利:我可以在 Ubuntu 10.04 上使用 C++11 功能,这正是我所追求的。

我注意到这篇文章是从 2005 年开始的,从那时起可能已经发生了很大的变化。它的建议是否仍然有效?有什么我应该注意的潜在问题吗?

4

5 回答 5

147

那篇博文很不准确。

据我所知,GCC 的每个主要版本都引入了 C++ ABI 更改(即具有不同的第一或第二版本号组件的版本)。

不对。自 GCC 3.4 以来引入的唯一 C++ ABI 更改是向后兼容的,这意味着 C++ ABI 已经稳定了近九年。

更糟糕的是,大多数主要的 Linux 发行版都使用 GCC 快照和/或修补它们的 GCC 版本,这使得在分发二进制文件时几乎不可能确切地知道您可能正在处理的 GCC 版本。

发行版的 GCC 补丁版本之间的差异很小,并且 ABI 没有变化,例如 Fedora 的 4.6.3 20120306(Red Hat 4.6.3-2)与上游 FSF 4.6.x 版本的 ABI 兼容,几乎可以肯定与任何 4.6 兼容。 x 来自任何其他发行版。

在 GNU/Linux GCC 的运行时库使用 ELF 符号版本控制,因此很容易检查对象和库所需的符号版本,如果您有一个libstdc++.so提供这些符号的符号,它将起作用,它是否是一个稍微不同的补丁版本都没关系从您的发行版的另一个版本。

但是如果要工作,则不能动态链接 C++ 代码(或任何使用 C++ 运行时支持的代码)。

这也不是真的。

也就是说,静态链接libstdc++.a是您的一种选择。

如果您动态加载库(使用 ),它可能不起作用的原因dlopen是,当您(静态)链接它时,您的应用程序可能不需要它所依赖的 libstdc++ 符号,因此这些符号不会出现在您的可执行文件中。这可以通过将共享库动态链接到来解决libstdc++.so(如果它依赖于它,这无论如何都是正确的做法。)ELF符号插入意味着您的可执行文件中存在的符号将被共享库使用,但其他符号不会存在于您的可执行文件中的内容将在libstdc++.so它链接到的任何地方找到。如果您的应用程序不使用dlopen,则无需关心。

另一种选择(也是我更喜欢的选择)是将更新libstdc++.so的应用程序与您的应用程序一起部署并确保在默认系统之前找到它libstdc++.so,这可以通过强制动态链接器查找正确的位置来完成,或者$LD_LIBRARY_PATH在运行时使用环境变量-时间,或者通过RPATH在链接时在可执行文件中设置一个。我更喜欢使用RPATH它,因为它不依赖于为应用程序正确设置的环境。如果您将应用程序与'-Wl,-rpath,$ORIGIN'(注意单引号以防止外壳尝试扩展)链接,$ORIGIN那么可执行文件将有一个告诉动态链接器在与可执行文件本身相同的目录中查找共享库。如果你把较新的RPATH$ORIGINlibstdc++.so在与可执行文件相同的目录中,它将在运行时找到,问题已解决。(另一种选择是将可执行文件/some/path/bin/和较新的 libstdc++.so 放入/some/path/lib/并链接到'-Wl,-rpath,$ORIGIN/../lib'或相对于可执行文件的任何其他固定位置,并将 RPATH 设置为相对于$ORIGIN

于 2012-12-29T14:22:57.957 回答
12

Jonathan Wakely 出色回答的一个补充,为什么 dlopen() 有问题:

由于 GCC 5 中的新异常处理池(请参阅PR 64535PR 65434),如果您 dlopen 和 dlclose 静态链接到 libstdc++ 的库,则每次都会出现(池对象的)内存泄漏。因此,如果您有任何机会使用 dlopen,那么静态链接 libstdc++ 似乎是一个非常糟糕的主意。请注意,这是一个真正的泄漏,而不是PR 65434中提到的良性泄漏。

于 2016-01-26T13:52:56.467 回答
3

您可能还需要确保不依赖动态 glibc。在生成的可执行文件上运行ldd并记下任何动态依赖项(libc/libm/libpthread 是常用的嫌疑人)。

额外的练习将是使用这种方法构建一堆涉及的 C++11 示例,并在真正的 10.04 系统上实际尝试生成的二进制文件。在大多数情况下,除非您对动态加载做了一些奇怪的事情,否则您会立即知道程序是正常运行还是崩溃。

于 2012-11-29T23:34:36.680 回答
3

Jonathan Wakely 关于 RPATH 的回答的附加组件:

只有当相关的 RPATH 是正在运行的应用程序的 RPATH 时,RPATH 才会起作用。如果您有一个通过其自己的 RPATH 动态链接到任何库的库,则该库的 RPATH 将被加载它的应用程序的 RPATH 覆盖。当您不能保证应用程序的 RPATH 与您的库的相同时,这是一个问题,例如,如果您希望您的依赖项位于特定目录中,但该目录不是应用程序 RPATH 的一部分。

例如,假设您有一个应用程序 App.exe,它对 GCC 4.9 的 libstdc++.so.x 具有动态链接依赖项。App.exe 通过 RPATH 解决了这种依赖关系,即

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

现在假设有另一个库 Dependency.so,它对 GCC 5.5 的 libstdc++.so.y 具有动态链接的依赖关系。这里的依赖是通过库的RPATH解决的,即

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

当 App.exe 加载 Dependency.so 时,它既不附加也不预置 library 的 RPATH。它根本不咨询它。唯一考虑的 RPATH 将是正在运行的应用程序的 RPATH,或本示例中的 App.exe。这意味着如果库依赖于 gcc5_5/libstdc++.so.y 中但不在 gcc4_9/libstdc++.so.x 中的符号,则库将无法加载。

这只是一个警告,因为我过去自己也遇到过这些问题。RPATH 是一个非常有用的工具,但它的实现仍然存在一些问题。

于 2019-06-19T17:24:05.743 回答
1

我想在 Jonathan Wakely 的回答中添加以下内容。

在 linux 上玩耍-static-libstdc++,我遇到了dlclose(). 假设我们有一个静态链接到的应用程序“A” libstdc++,它在运行时加载动态链接到libstdc++插件“P”。没关系。但是当'A'卸载'P'时,会发生分段错误。我的假设是卸载后libstdc++.so,“A”不再可以使用与libstdc++. 请注意,如果“A”和“P”都静态链接到libstdc++,或者如果“A”是动态链接而“P”是静态链接,则不会出现问题。

摘要:如果您的应用程序加载/卸载可能动态链接到的插件,则libstdc++该应用程序也必须动态链接到它。这只是我的观察,我想听听你的意见。

于 2019-02-27T19:16:30.903 回答