【问题标题】:Linking libstdc++ statically: any gotchas?静态链接 libstdc++:有什么问题吗?
【发布时间】:2012-11-18 03:50:24
【问题描述】:

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

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

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

【问题讨论】:

  • 不,静态链接到 libstdc++ 并不意味着这一点。如果它确实暗示了 -static-libstdc++ 选项将毫无意义,您只需使用 -static
  • @JonathanWakely -static 在某些 ubuntu 1404 系统中会得到 kernel too old 错误。 glibc.so 就像窗口中的kernel32.dll,它是操作系统接口的一部分,我们不应该将它嵌入到我们的二进制文件中。您可以使用objdump -T [binary path] 来查看它是否动态加载libstdc++.so。对于golang程序员,你可以在import "C"之前添加#cgo linux LDFLAGS: -static-libstdc++ -static-libgcc
  • @bronzeman,但我们谈论的是-static-libstdc++ 而不是-static 所以libc.so 不会被静态链接。
  • @NickHutchinson 链接到的博客文章不见了。这个 SO question 是此处相关术语的热门搜索。您能否在您的问题中复制该博客文章中的关键信息,或者如果您知道它的位置,请提供一个新链接?

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


【解决方案1】:

那篇博文非常不准确。

据我所知,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' 链接您的应用程序(注意单引号以防止shell 尝试扩展$ORIGIN),那么可执行文件将具有$ORIGINRPATH,它告诉动态链接器在与可执行文件本身相同的目录。如果您将较新的libstdc++.so 放在与可执行文件相同的目录中,它将在运行时找到,问题就解决了。 (另一种选择是将可执行文件放在/some/path/bin/ 中,将较新的libstdc++.so 放在/some/path/lib/ 中,并与'-Wl,-rpath,$ORIGIN/../lib' 或相对于可执行文件的任何其他固定位置链接,并设置相对于$ORIGIN 的RPATH)

【讨论】:

  • 这个解释,尤其是关于 RPATH 的解释,是光荣的。
  • 在 Linux 上将 libstdc++ 与您的应用程序一起提供是不好的建议。谷歌“steam libstdc++”可以看到这带来的所有戏剧性。简而言之,如果您的 exe 加载了想要再次 dlopen libstdc++(如 radeon 驱动程序)的外部库(如 opengl),则这些库将使用 your libstdc++,因为它已经加载,而不是它们自己的,这是他们需要和期望的。所以你又回到了原点。
  • @cap,OP 专门询问是否部署到系统 libstdc++ 较旧的发行版。 Steam 的问题是他们捆绑了一个比系统旧的 libstdc++。这可以通过让 RPATH 指向包含libstdc++.so.6 符号链接的目录来解决,该符号链接在安装时设置为指向捆绑的库或系统库(如果它较新)。还有更复杂的混合链接模型,如 Red Hat DTS 所使用的,但您自己很难做到。
  • 嘿伙计,很抱歉,如果我不希望我的用于发送向后兼容二进制文件的模型包括“信任其他人保持 libstdc++ ABI 兼容”或“在运行时有条件地链接 libstdc++”。 ..如果这让一些地方不满意,我能做什么,我没有不尊重的意思。如果你还记得 memcpy@GLIBC_2.14 戏剧,你不能因为信任问题而责怪我 :)
  • 我不得不使用'-Wl,-rpath,$ORIGIN'(注意rpath前面的'-')。我无法编辑答案,因为编辑必须至少为 6 个字符 ....
【解决方案2】:

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

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

【讨论】:

  • 函数__gnu_cxx::__freeres() 似乎至少为这个问题提供了一些帮助,因为它释放了池对象的内部缓冲区。但对我来说,对于之后意外抛出的异常,调用这个函数有什么含义还不清楚。
【解决方案3】:

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

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

【讨论】:

  • 依赖动态glibc有什么问题?
  • 我相信至少在一段时间之前 libstdc++ 暗示了对 glibc 的依赖。不确定今天的情况。
  • libstdc++ 确实依赖于 glibc(例如 iostreams 是根据 printf 实现的),但只要 Ubuntu 10.04 上的 glibc 提供较新的 libstdc++ 所需的所有功能,依赖于动态 glibc,实际上强烈建议不要静态链接到 glibc
【解决方案4】:

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 将是正在运行的应用程序的 RPATH,或本示例中的 App.exe。这意味着如果库依赖于 gcc5_5/libstdc++.so.y 中但不在 gcc4_9/libstdc++.so.x 中的符号,则库将无法加载。

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

【讨论】:

  • 所以共享库的 RPATH 有点毫无意义!我希望,在过去的 2 年里,他们在这方面对 Linux 有所改进……
【解决方案5】:

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

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

总结:如果您的应用程序加载/卸载可能动态的插件 链接到libstdc++,应用程序也必须动态链接到它。 这只是我的观察,我想得到你的 cmets。

【讨论】:

  • 这可能类似于混合 libc 实现(比如动态链接到一个插件,然后动态链接 glibc,而应用程序本身静态链接到 musl-libc)。 musl-libc 的作者 Rich Felker 声称,这种情况下的问题是 glibc 内存管理(使用sbrk)做出了一定的假设,并且几乎希望在一个进程中单独存在......不确定这是否是不过,仅限于特定的 glibc 版本或其他版本。
  • 人们仍然没有看到windows heap接口的优势,它能够在一个进程中处理多个独立的libc++/libc副本。这样的人不应该设计软件。
  • @FrankPuck 拥有相当多的 Windows 和 Linux 经验我可以告诉你,当 MSVC 是决定使用什么分配器以及如何使用的一方时,“Windows”的方式对你没有帮助.我在 Windows 上看到堆的主要优点是您可以分发零碎的东西,然后一举释放它们。但是使用 MSVC,您仍然会遇到上述问题,例如当传递由另一个 VC 运行时分配的指针时(发布与调试或静态与动态链接)。所以“Windows”也不能幸免。必须注意这两个系统。
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 2017-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多