/* foo.c */int foo() { return 42; }int bar() { return foo() + 1; }int baz() { return bar() - 1; }
编译上述文件,并且查看符号:
$ gcc -fPIC -shared -o libfoo.so foo.c && nm -D libfoo.so | grep ' T '0000000000000718 T _fini00000000000005b8 T _init00000000000006b7 T bar00000000000006c9 T baz00000000000006ac T foo
可见在默认情况下,所有的符号都被导出了 。现在我们创建 version 脚本:libfoo.version,限制一些符号的可见性,内容如下所示:
FOO {global: bar; baz; # 只导出 bar 和 bazlocal: *;# 隐藏其他的符号};
然后把它传递给链接器,重新编译链接,再查看相应的符号:
$ gcc -fPIC -shared -o libfoo.so foo.c -Wl,--version-script=libfoo.version$ nm -D libfoo.so | grep ' T '00000000000005f7 T bar0000000000000609 T baz
与预期一致 。
链接器的 --exclude-libs 选项链接器的这个选项可以不导出(exclude)指定静态库的符号,该选项可以接收多个参数,各个参数用逗号或者冒号分开:
$ ... --exclude-libs lib,lib,lib
--exclude-libs 在 i386 PE 平台和 ELF 平台可用,对于 ELF 平台来说,该选项将会把指定库里的符号改为本地隐藏状态,具体可参考这里 。稍后将看到实例 。
几个方法的特点
- 要使用编译器的 -fvisibility 需要从代码层面修改,工作量略大;
- 编写链接器的版本脚本需要明确知道每一个符号,处理复杂库比较痛苦;
- 使用链接器的 --exclude-libs,虽然简单,但是要求传递的参数是静态库 。
实验实验现象现在编写简易代码模拟本文开头遇到的问题 。首先编写 lib_v1.0.cpp,表示版本 1.0 的库:
// lib_v1.0.cppfloat foo() {return 1.0;}
然后编写 lib_v1.1.cpp,表示版本 1.1 的库:// lib_v1.1.cppfloat foo() {return 1.1;}
接着编写 wrapper.cpp,调用库函数 foo():// wrapper.cppfloat foo();float wfoo() {return foo();}
我们首先将两个版本的库编译出来:$ g++ -c lib_v1.0.cpp -o lib_v1.0.o$ ar cr libv1.0.a lib_v1.0.o$$ g++ -c lib_v1.1.cpp -o lib_v1.1.o$ ar cr libv1.1.a lib_v1.1.o
我们还有封装了版本 1.0 的库的 libwrapper.so ,编译之:$ g++ -fPIC wrapper.cpp -L./ -lv1.0 -shared -o libwrapper.so
此时我们得到了三个库:- libv1.0.a
- libv1.1.a
- libwrapper.so(封装了 libv1.0)
// test.cpp#include <IOStream>float foo();float wfoo();int main() {float f = foo();float wf = wfoo();std::cout << f << ", " << wf << std::endl;return 0;}
编译 test.cpp,并同时链接 libv1.1.a 和 libwrapper.so,然后执行之,得到如下输出:$ g++ test.cpp -L./ -lv1.1 -lwrapper -o test$ ./test1.1, 1.1
可以看出,此处的输出与直觉(1.1, 1)并不一致 。现在我们交换 libv1.1.a 和 libwrapper.so 的链接顺序:$ g++ test.cpp -L./ -lwrapper -lv1.1 -o test$ ./test1, 1
分析和解决同样,输出还是与直觉(1, 1.1)不一致,怎么回事呢?结合前文的分析思考下,其实是不难理解的,这个现象背后隐含的原理也可以解释和解决文章开头遇到问题:一个程序链接不同版本的同一个库,可能会崩溃 。隐藏内容,请点击文章末尾的“了解更多”查看 。
此时,无论我们如何交换链接顺序,都能得到预期结果:
$ g++ test.cpp -L./ -lwrapper -lv1.1 -o test$ ./test 1.1, 1$$ g++ test.cpp -L./ -lv1.1 -lwrapper -o test$ ./test 1.1, 1
我们的实验虽然简单,但是原理是通用的 。将上述方法应用到 MNN 不同版本库的冲突问题解决上,确实解决了问题 。小结应用程序的编译链接过程,很多时候是处理符号的过程 。程序同时链接不同版本的同一个库时,只要解决好符号问题,就能避免冲突崩溃 。这方面的知识需要继续提升啊,不然再遇到类似的问题,就要连猜带蒙了 。
推荐阅读
- Java,ShardingSphere,Sharding-JDBC,分库分表的入门程序案例
- C++实现线程池
- 一个设置让你的电脑网速和程序员的网速一样快
- 编写Linux下的UDP Client/Server程序
- 微信小程序开发之hello world
- 了解一下微信小程序的框架
- 医学生|程序员也有“春天”,“理想男友”职业排行被更新,榜首实至名归
- UDP协议以及基于UDP的网络通讯程序
- 不能爬小程序,叫什么会爬虫
- 2021年校招程序员之阿里四个部门的十轮面试问题