事情源于: 两位同事进行连调时,A同事调用B同事的so, 运行了一会后直接出core了. 后来发现是B同事的so的结构体名和A同事的类名冲突了.
静态库
测试程序很简单如下:
libA.h
libA.c
编译选项:
main.c
libA 和 main里面同时有个display函数.对 main.c进行编译 并连接如下:
成功编译!
运行输出: Hello Main
去掉main.c中的display()的实现.再次编译 ,输出为: Hello libA , 可以看出:
若 main中实现了此函数,则会覆盖掉静态库中相同的函数.
让我们再细致分析静态是怎么连接的.
从main.c这段程序开始:
只生成目标文件也即gcc -c main.c
然后查看main.o的符号表信息:
可以看出display函数是U的,也即未定义的!
把它和两个东西进行链接 1. libA.o 2.libA.a
连接结果是: 两者都可以成功连接 !
再把main.c的display的实现加进去. 然后查看main.o的符号表:
display函数也成为定义的了,但是如果我再和1. libA.o 2.libA.a 进行连接 会发生什么问题呢?
1.libA.o
连接libA.o的时候报 display多次定义.
再连接libA.a时就跟之前一样 是可以成功连接,并可以运行的.
这说明ld连接器在连接时对目标文件和静态链接库处理是不一样的. 为什么不一样呢? 其实目标文件在进行连接时会把相同性质的段合并到一起,而且收集所有目标文件的符号表中的符号定义和符号引用放到global table 中, 在这个过程中如果出现重复的 就会报刚才的错误了.,
2.libA.a
链接libgA.a时确能正常生成 a.out ,libA.a只不过是libA.o的打包,但是为什么可以正常编译成功呢? 其实这里面有个强弱符号的问题. 在连接器链接时 ,会进行符号解析, 符号解析时,符号表有强弱之分.对于同名强弱符号要符号下面3个规则:
1.只允许一个强符号存在
2.一个强符号和多个弱符号,则以强符号为主
3.若有多个弱符号,则任选其一.
另外,函数和已经初始化的全局变量为强符号,未初始化的全局变量为弱符号.
所以这就出现我们刚才遇到的情况main.o 和 libA.o一起链接时,是强符号的链接 ,所以会报错.而静态库是多个可重定位的目标文件的集合. 连接器在符号解析阶段, 会从左到右按照出现的先后顺序来加载可重定位目标文件和静态库,连接器会维护一个可重定位目标文件的集合E, 这个集合的文件会被合并起来形成可执行文件, 和一个未解析的符号(引用了,尚未定义的符号)集合U,和一个已经定义的符号D.
当ld链接每个文件时,它会从左到右开始链接,并且判断当且文件是目标文件还是静态库!
如果是目标文件,那么连接器会把它添加进E中,并用它来解析U和D,如果遇到两个相同符号则按照上面三个规则来处理. 这样就会出现和之前的例子多次定义的情况了!
如果是静态库,则连接器首先查看U中是否有未解析的符号和静态库中的目标文件的符号一致,如果有,则把那个静态库中的相应目标文件添加进E集合中.在同时修改U和D集合. 如果没有则丢弃目标文件.
好说到这,我们来看下面的例子: 开始的例子中main.c中的display已经定义 也就是说没有U集合.当它和静态库linA.a进行链接时,发现U集合中没有定义就把libA.a中的目标文件libA.o丢弃了. 假如我们在main.c中加入 未定义的display2(); 在libA中进行定义.这样会不会和我们所说的那样呢?
main.c
通过 readelf -s main.o 来查看main.o ELF符号表:
可以看出 display2是undefined的! puts是属于动态库的 动态调用glibc的
libA.c
进行链接libA文件:
果然如前文所说的那样.
我们继续,当连接器最后发现U集合为非空的,则连接器就会输出一个错误并且终止程序!
解决这种情况也很简单,只要在重定义的函数前加上static来限制它的作用域就可以了, 尤其在大型的工程构建中, 这种事经常发生, 对于C++ 可以使用namespace ,C的话可以定义好大家的命名规范 或者就用 static来解决.
动态库
我们再回过头来看看开始我同事出的问题,bug代码(重新还原bug原型,非工程代码)如下:
动态库的代码: libdy.so
.cpp
.h
编译选项:
main.cpp
编译选项:
然后直接运行:
出现Core.
这是为什么呢? 其实主要是rdynamic这个选项出现的问题. 动态库在运行时加载时,rdynamic选项会把main.o里面的ServerConf变成全局可见的, 显然载入动态库时, 调用错了.
我们再次这样编译主程序:
然后也直接运行
运行OK!
来看下没加rdynamic的符号表.
再看下加rdynamic的符号表:
明显多了很多.
解决这种情况, 其实给main.cpp里面的ServerConf加上namespace就可以了,那动态库是否需要加呢? 动态库和动态库 和 静态库的的问题可以参见这篇文章:
http://blog.csdn.net/crazyjixiang/article/details/6614250(深析静态链接库和动态链接库相同函数覆盖及库调用顺序问题)
使用Cmake的童鞋要注意了,cmake编译时默认是加这个选项的, 我觉得有必要加. 但是这个又给不了解内部机制的童鞋造成莫名其妙的错误. 我建议还是去了解下.
另外再次请注意下dlopen的参数, 也就是flag标志,详细,请参见linux Man , 下面逐个详解各个flag参数, 只有知己知彼方能掌控.
RTLD_LAZY:
LAZY 也就是说,当动态库中声明了未定义的函数或变量,并且在调用的函数中使用了. 在dlopen时不会去解析这个未定义的符号的地址.
RTLD_NOW:
NOW 和LAZY相反,当执行dlopen时就会去解析动态库未定义的函数的地址.
举例:
这是动态库的程序,func()只是个声明. 如果main程序以lazy打开则不会报错,而以NOW打开则会dlopen error!
RTLD_GLOBAL:
GLOBAL 可以看出,所有解析出来的符号是全局的,任何链接库都可以使用. 刚才那个rdnamic是把可执行程序变为全局的,而这个GLOBAL是把动态库变为全局可见的 .
另外 ld 有个选项是 -export-dynamic: 其实就和g++的编译选项-rdnamic的意思一样的:
When creating a dynamically linked executable, add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects
at run time. If you do not use this option, the dynamic symbol table will normally contain only those symbols which are referenced by some dynamic object mentioned in the link. If you usedlopen
to load
a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic object, then you will probably need to use this option when linking the program itself.
ld 更多选项的可以 参见:http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_3.html
.
分享到:
相关推荐
linux静态库和动态库的区别
使用简单的程序展示了C++调用动态库和静态库的方法。 文件结构: exe:笔者部署可运行文件,因开发环境版本不同,可能存在无法直接使用的情况,两个部署文件(copy_ldd.sh及useLib1.sh)可以结合文章分享的(ubuntu下...
压缩包内包含两个文件夹curl_静态库和curl_动态库,内容说明如下: curl_静态库下面: bin子目录包含curl.exe及动态库libcurl.dll include子目录包含头文件 lib子目录包含动态库的导入库文件libcurl.lib curl_...
函数库分为静态库和动态库两种。 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库...
演示Qt静态链接库与动态链接库的创建与使用。环境:windows xp Qt4, MinGW编译器环境。供备忘和参考。
Android Studio 基于cmake 链接静态库,动态库,博客地址:https://blog.csdn.net/dreams_deng/article/details/104540775
liunx静态库与动态库链接装载与库.zip
linux下面的库文件有两种:静态链接库(xx.a)和动态链接库(xx.so)。 当一个程序使用静态链 接库,那么当link的时候,连接器会把所需要的函数拷贝到源程序里面,这样,当编译完以后,静态链接库就可以不需要了。如果...
1:模块化CMakeLists 的写法 2:每个模块先生成静态库 3:把各个静态库生成一个动态库 4:生成一个测试demo 验证动态库
动态库与静态库的区别,各自的优缺点,动态库和静态库的创建方法与连接方法
简单的Linux下的静态库和动态链接库的编译方法和使用发发
vs2015编译的librdkafka动态库和静态库 v1.6.1版本
文档介绍了linux下静态库和动态库生成以及使用问题。文档介绍了linux下静态库和动态库生成以及使用问题。
只要是qt在android开发中调用动态库和静态库的例子。
介绍静态链接库和动态链接库作用和区别,制作静态库和动态库的方法,编程测试自己编写的两种库
源码经过编译生成对象文件,对象文件进一步处理可以生成可执行程序或者库文件。...对于可执行程序和动态库,此“进一步处理”指的是“链接”,使用gcc命令。 对于静态库,此“进一步处理”指的是“打包”,使用ar命令。
在这里将以举例的形式详述...静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
C与C++接口、静态库、动态库的互调。使用build.sh脚本方式编译,直接运行main程序即可。
本文详细介绍了静态链接库与动态链接库的区别,适合于那些对二者概念分不清楚的同学,以及如何创建一个静态库和动态库的方法
.dll是在你的程序运行的时候才连接的文件,因此它是一种比较小的可执行文件格式,.dll还有其他的文件格式如.ocx等,所有的.dll文件都是可执行。