静态链接libstdc ++:任何陷阱?

时间:2012-11-29 23:12:42

标签: c++ linux gcc static-libraries libstdc++

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

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

我注意到这篇文章是从2005年开始的,也许从那以后发生了很多变化。它的建议仍然是最新的吗?我应该注意哪些潜在的问题?

5 个答案:

答案 0 :(得分:118)

该博客文章非常不准确。

  

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

不正确。自GCC 3.4以来引入的唯一C ++ ABI变化是向后兼容的,这意味着C ++ ABI已经稳定了近9年。

  

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

GCC的分发修补版本之间的差异很小,而不是ABI更改,例如: Fedora的4.6.3 20120306(Red Hat 4.6.3-2)与上游FSF 4.6.x版本兼容,几乎可以肯定与任何其他版本的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'相关联(请注意单引号以防止shell尝试展开$ORIGIN),那么可执行文件将RPATH $ORIGIN告诉动态链接器在与可执行文件本身相同的目录中查找共享库。如果将较新的libstdc++.so放在与可执行文件相同的目录中,它将在运行时找到,问题就解决了。 (另一种选择是将可执行文件放在/some/path/bin/和更新的libstdc ++中。所以在/some/path/lib/中并与'-Wl,-rpath,$ORIGIN/../lib'或相对于可执行文件的任何其他固定位置链接,并将RPATH设置为相对于$ORIGIN

答案 1 :(得分:9)

Jonathan Wakely的优秀答案之一,为什么dlopen()存在问题:

由于GCC 5中的新异常处理池(参见PR 64535PR 65434),如果你dlopen和dlclose一个静态链接到libstdc ++的库,你将得到内存泄漏(池对象)每次。因此,如果您有可能使用dlopen,静态链接libstdc ++似乎是一个非常糟糕的主意。请注意,这是一个真正的泄漏,而不是PR 65434中提到的良性泄漏。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

Jonathan Wakely关于RPATH的回答的附加内容:

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时,它既不添加也不添加库的RPATH 。它根本不咨询它。唯一考虑的RPATH是正在运行的应用程序(在本示例中为App.exe)。这意味着,如果该库依赖于gcc5_5 / libstdc ++。so.y中的符号而不是gcc4_9 / libstdc ++。so.x中的符号,则该库将无法加载。

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

相关问题