【发布时间】:2009-02-14 10:05:35
【问题描述】:
如何在 Linux/gcc 上创建与 pthreads 和 libstdc++ 静态链接的共享对象?
【问题讨论】:
-
你有没有尝试在编译时将 -static 和 -fPIC 传递给 g++?
标签: linux gcc linker pthreads libstdc++
如何在 Linux/gcc 上创建与 pthreads 和 libstdc++ 静态链接的共享对象?
【问题讨论】:
标签: linux gcc linker pthreads libstdc++
在我按照描述的方式回答您的问题之前,我会指出,您最终要达到的目标并不完全清楚,并且可能有更好的解决方案来解决您的问题。
也就是说 - 尝试执行您所描述的操作有两个主要问题:
一个是,您需要将libpthread 和libstdc++ 分解为它们所使用的目标文件。这是因为 ELF 二进制文件(在 Linux 上使用)有两个级别的“运行时”库加载 - 即使可执行文件是静态链接的,加载程序也必须在执行时加载二进制文件中的静态链接库,并映射正确的内存地址。这是在动态加载(共享对象)并映射到共享内存的库的共享链接之前完成的。因此,共享对象不能与此类库静态链接,因为在加载对象时,所有静态链接库都已加载。这是与静态库和普通目标文件链接之间的一个区别 - 静态库不仅像任何目标文件一样粘贴到可执行文件中,而且还包含在加载时引用的单独表。 (我相信这与 MS-DOS 和经典 Windows 中更简单的静态库 .LIB 文件形成对比,但这些文件可能比我记得的要多)。
当然你实际上不必分解libpthread和libstdc++,你可以使用构建它们时生成的目标文件。收集它们可能有点困难(查找这些库的最终 Makefile 规则引用的对象)。而且您必须直接使用ld 而不是gcc/g++ 来链接,以避免与动态版本链接。
第二个问题是必然的。如果您执行上述操作,您肯定会拥有您要求构建的共享对象/动态库。但是,它不会很有用,因为一旦您尝试将使用 libpthread/libstdc++(后者是任何 C++ 程序)的常规可执行文件与此共享对象链接,它将因符号冲突而失败 - 符号您链接共享对象所针对的静态libpthread/libstdc++ 对象将与该可执行文件使用的标准libpthread/libstdc++ 中的符号发生冲突,无论它是与标准库动态链接还是静态链接.
您当然可以尝试在共享库使用的libstdc++/libpthread 中隐藏静态对象中的所有符号,以某种方式将它们设为私有,或者在链接时自动重命名它们,以便没有冲突。然而,即使你得到它的工作,你会在运行时发现一些不希望的结果,因为libstdc++/libpthread 在全局变量和结构中保留了相当多的状态,你现在会有重复并且每个都不知道另一个。这将导致这些全局数据与底层操作系统状态(例如文件描述符和内存边界)之间的不一致(可能还有标准 C 库中的一些值,例如 libstdc++ 的 errno 和 @987654341 的信号处理程序和计时器@。
为了避免过于宽泛的解释,我要补充一句:有时可能有合理的理由想要静态链接到libstdc++甚至libc这样的基本库,即使这些库的最新系统和版本变得更加困难(由于与加载器的一些耦合和使用的特殊链接器技巧),它绝对有可能 - 我做到了几次,并知道其他情况下它仍然完成。 但是,在这种情况下,您需要静态链接整个可执行文件。与标准库的静态链接结合动态链接与其他对象通常是不可行的。
编辑:我忘记提及但重要的是要考虑的一个问题是 C++ 特定的。不幸的是,C++ 的设计不能很好地与对象链接和加载的经典模型(在 Unix 和其他系统上使用)一起工作。这使得 C++ 中的共享库无法真正实现应有的可移植性,因为诸如类型信息和模板之类的很多东西在对象之间没有完全分离(通常在编译时与许多实际的库代码一起从头文件中获取) )。出于这个原因,libstdc++ 与 GCC 紧密耦合,使用 g++ 的一个版本编译的代码通常只能与 g++ 的这个(或非常相似的)版本的 libstdc++ 一起使用。如果您曾经尝试使用 GCC 4 构建程序,并且在您的系统上使用 GCC 3 构建的任何重要库,您肯定会注意到,这不仅仅是libstdc++。如果您想要这样做的原因是试图确保您的共享对象始终与构建它所针对的 libstdc++ 和 libpthread 的特定版本链接,那么这将无济于事,因为使用不同/不兼容的程序libstdc++ 也将使用不兼容的 C++ 编译器或 g++ 版本构建,因此无论如何都无法与您的共享对象链接,除了实际的 libstdc++ 冲突。
如果您想知道“为什么不这样做更简单?”,值得深思的一般反思:C++ 可以很好地与动态/共享库一起工作(意味着跨编译器的兼容性,以及将动态库替换为具有兼容接口的另一个版本,而无需重新构建使用它的所有内容),不仅需要编译器标准化,还需要在操作系统的加载程序、对象和库文件的结构和接口以及链接器需要大大扩展,超出常见操作系统上使用的相对简单的 Unix 经典(Microsoft Windows、基于 Mach 的系统和 NeXTStep 亲属,如 Mac OS、VMS 亲属和一些大型机系统也包括)对于今天的本地构建代码。链接器和动态加载器需要了解诸如模板和类型之类的事情,在某种程度上具有小型编译器的功能才能实际使库的代码适应给它的类型 - 并且(个人主观观察)似乎更高级的中间中间代码(连同高级语言和即时编译)比对本机对象格式和链接器。
【讨论】:
您在单独的评论中提到您正在尝试将 C++ 库移植到嵌入式设备。 (我在这里添加一个新答案,而不是在此处编辑我的原始答案,因为我认为其他对这个原始问题感兴趣的 StackOverflow 用户可能仍然对该答案感兴趣)
显然,根据您的嵌入式系统的精简程度(我没有太多嵌入式 Linux 经验,所以我不确定最有可能的是什么),您当然可以在其上安装共享的 libstdc++并像其他方式一样动态链接所有内容。
如果与libstdc++ 动态链接对您不利或无法在您的系统上运行(嵌入式系统有很多不同级别,人们无法知道),并且您需要针对静态libstdc++ 进行链接,然后正如我所说,您唯一真正的选择是使用库静态链接可执行文件和libstdc++。您提到将库移植到嵌入式设备,但如果这是为了在您在设备上编写或构建的某些代码中使用它,并且您不介意静态 libstdc++,那么静态链接所有内容(可能除了 @ 987654328@) 应该没问题。
如果libstdc++ 的大小有问题,并且您发现您的库实际上只使用了它的一小部分接口,那么我仍然建议您首先尝试通过仅链接来确定您可以节省的实际空间您需要的零件。它可能重要与否,我从来没有深入研究过libstdc++,我怀疑它有很多内部依赖项,所以虽然您肯定不需要某些接口,但您可能仍然或可能不依赖于一个大的它的内部结构的一部分 - 我不知道也没有尝试过,但它可能会让你大吃一惊。您可以通过将使用库的二进制文件与它的静态构建和libstdc++ 链接(当然不要忘记剥离二进制文件)并将生成的可执行文件的大小与总大小进行比较来获得一个想法(stripped) 可执行文件与库的完整 (stripped) 共享对象和 libstdc++ 动态链接在一起。
如果您发现大小差异很大,但又不想静态链接所有内容,请尝试通过在不包含某些您知道不需要的部分的情况下重建 libstdc++ 来减小它的大小(有 configure-它的某些部分的时间选项,您还可以尝试在libstdc++.so的最终创建时删除一些独立对象。有一些工具可以优化库的大小 - 搜索网络(我记得一个来自一家名为@的公司987654321@,但现在在他们的网站上看不到,还有一些其他的。
除了上面直截了当的之外,还有一些想法和建议可以考虑:
您提到您使用uClibc,我从来没有摆弄过自己(我在嵌入式编程方面的经验要原始得多,主要涉及嵌入式处理器的汇编编程和使用最少的嵌入式库进行交叉编译)。我假设你检查了这个,我知道 uClibc 旨在成为一个轻量级但相当完整的标准 C 库,但不要忘记 C++ 代码几乎不独立于 C 库,g++ 和 libstdc++ 依赖于在一些相当微妙的事情上(我记得libc 在某些专有 Unix 版本上的问题),所以我不会假设g++ 或 GNU libstdc++ 实际上可以与uClibc 一起工作而无需尝试 - 我不记得了在uClibc 页面中看到它。
此外,如果这是一个嵌入式系统,请考虑其性能、计算能力、整体复杂性和时序/简单性/可靠性要求。考虑所涉及的复杂性,并考虑在您的嵌入式系统中使用 C++ 和线程是否合适,如果系统中没有其他东西使用这些,是否值得为该库引入。可能是,不知道我无法分辨的库或系统(同样,如今嵌入式系统范围如此广泛)。
在这种情况下,我偶然发现了一个快速链接,寻找uClibc——如果你正在使用uClibc在嵌入式系统上工作,并且想在上面使用C++代码——看看在@987654322@。我不知道你需要多少标准 C++ 东西并且它已经支持,而且它似乎是一个正在进行的项目,所以不清楚它是否处于对你来说已经足够好的状态,但假设你的工作也在仍在开发中,对于您的嵌入式工作,它可能是 GCC 的 libstdc++ 的一个很好的替代品。
【讨论】:
我认为this guy 很好地解释了为什么这没有意义。使用您的共享对象但使用不同 libstdc++ 的 C++ 代码可以正常链接,但无法正常工作。
【讨论】: