C++程序同时使用同一个库的不同版本,为什么运行时崩溃了?

最近在编写C++工程时,使用了 MNN 库(libMNN.a,1.0 版本) 。是这么使用的:我首先把 MNN 封装了下,打包成一个新的库,暂且称之为 wrApper.so 吧,提供给客户使用 。不久后客户反馈问题,使用我提供的 wrapper.so 后,程序编译正常,但是运行时崩溃了 。。。分析原因后,知道客户除了使用我提供的 wrapper.so 之外,也使用了 MNN 库,只不过版本是 1.1 版本 。

C++程序同时使用同一个库的不同版本,为什么运行时崩溃了?

文章插图
为什么运行时崩溃了?
把这个问题抽象概括一下,就是一个程序链接不同版本的同一个库,可能会崩溃,这是为什么呢?要如何解决呢?回答这两个问题之前,正好趁机对静态库和动态库做深入一点的理解 。
不想看理解,只想看解决方案的直接翻到最后 。
静态库(static libraries)在 linux 系统中,静态库通常以 .a 结尾,例如 libg++.a 。使用 ar 命令可以将一系列目标文件(.o 文件)打包成静态库,因此,静态库的本质其实就是 .o 文件的集合,所以它的基础表现与 .o文件没有区别——在链接时(link time),链接器从静态库中搜索所有的可见全局函数/变量符号,并且把这些符号复制到二进制文件(通常是可执行程序)中 。
动态库/共享库(shared libraries)大多数现代操作系统(Linux、windows 等)都支持动态链接库,也即支持在程序运行时(runtime)链接动态库 。动态链接库的文件名在 Linux 中通常以 .so 结尾,在 Windows 中则通常以 .dll 结尾 。
使用编译器/链接器可以将 .o 文件打包成动态库,通常来说,要制作动态库,编译器需将 .o 文件编译为 PIC(Position Independent Code,位置独立代码),例如使用 gcc/g++ 编译时指定 -fPIC选项 。
$ g++ test.cpp -fPIC ...动态库允许多个应用程序共享同一个库,并不把动态库的代码复制到二进制文件中,因此相比于链接静态库,同等条件下,链接动态库的的程序具备更小的 size 。在运行时,动态库允许二进制文件访问库内的所有符号,即使在链接时没有用到这些符号 。
C++程序同时使用同一个库的不同版本,为什么运行时崩溃了?

文章插图
【C++程序同时使用同一个库的不同版本,为什么运行时崩溃了?】 
因为动态库通常是 PIC,所以就算多个应用程序链接的是一个动态库,在这些程序中,动态库函数也可以是不同的地址 。
动态库的名称中还可以包含版本控制信息,例如 libg++.so.2.7.1,这个版本控制一般依赖于体系架构 。动态库的版本信息可以在 SONAME 域中编码 。一般来说,动态库的 SONAME 和它的文件名是相同的,例如/usr/lib/libgxx.so.2.1.0 的 SONAME 是 libgxx.so.2.1.0 。
值得注意的是,如果我们不更改动态库的 SONAME,更改共享库的文件名,然后指定给链接器更改文件名后的动态库,那么在运行时,二进制文件可能会报错:找不到指定的库 。
和静态库不同,动态库程序需要在运行时链接,因此必须保证程序能够找到动态库,通常程序会从一些特殊的目录、环境变量里搜索需要链接的动态库,例如在 Linux 中,程序会从 LD_LIBRARY_PATH环境变量中搜索需要的动态库 。
二进制文件本身也可以在其内部编码存储要搜索的动态库所在路径列表(RPATH),这样做更好一点,因为不需要用户再手动指定库的搜索环境变量 。
动态库和静态库都是 ELF 文件吗?
C++程序同时使用同一个库的不同版本,为什么运行时崩溃了?

文章插图
 
静态库正如前文所述,静态库不是可执行的文件,它只是一系列 .o 文件的集合 。鉴于 .o 文件是 ELF 文件,我们可以说静态库是 .o 文件的集合 。
所谓的“链接静态库到程序”,并不是指静态库本身链接到程序 。静态库被传递给链接器后,链接器从静态库中提取出 .o 文件,然后从这些 .o 文件中挑选出自己需要的使用 。
这里再强调一下,静态库是 ELF 文件的集合,它本身并不是 ELF 文件 。虽说典型的 ELF 解析工具(例如 objdump,readelf,nm)能够解析静态库,但这是因为它们知道静态库的本质,所以解析输出的信息其实是静态库中 .o 文件的信息列表 。
例如,我们有目标文件 test.o,执行下面的命令将其打包为静态库 libtest.a:
$ ar cr libtest.a test.o


推荐阅读