事情源于: 两位同事进行连调时,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
.
分享到:
相关推荐
库通常分为两大类:静态库(static libraries)和动态库(dynamic libraries)。这两种库各有其优势和适用场景,在开发过程中合理选择和使用它们对于提高程序性能和维护性至关重要。 #### 二、静态库 **静态库**主要在...
VS2008静态库和动态库的创建和调用是指在VS2008中创建和使用静态库和动态库的过程。静态库是一种编译后生成的库文件,可以被多个工程使用,而动态库则是一种在运行时加载的库文件,提供了更多的灵活性。 静态库的...
首先,我们要理解静态库和动态库的区别。静态库(.a 或 .lib)是在编译时将库代码直接嵌入到目标程序中,形成一个完整的可执行文件;而动态库(.so 或 .dll)则是在运行时被加载,多个程序可以共享同一份库资源,...
通过压缩包中的"linux动态库及静态库的创建和使用"文件,你可以找到更具体的实践教程和示例代码,包括如何创建、链接和使用这两种类型的库,以及解决可能出现的问题。这些资料将帮助你深入理解和掌握Linux环境下的...
本教程将深入探讨如何在Windows XP环境下,利用Qt 4和MinGW编译器创建和使用静态库与动态库。 首先,让我们理解静态库和动态库的基本概念。静态库(.lib文件)是将库函数编译到目标代码中,形成一个完整的可执行...
使用简单的程序展示了C++调用动态库和静态库的方法。 文件结构: exe:笔者部署可运行文件,因开发环境版本不同,可能存在无法直接使用的情况,两个部署文件(copy_ldd.sh及useLib1.sh)可以结合文章分享的(ubuntu下...
动态库和静态库是两种常见的库类型,它们在编译和链接过程中扮演着不同的角色。在这篇文章中,我们将详细介绍动态库和静态库的创建、连接方法及其优缺点。 一、静态库 静态库是一种链接库,它在编译时期将目标文件...
通过以下步骤,我们可以生成静态库`libtemp.a`和动态库`libtemp.so`,然后分别链接生成可执行程序`mystatic`和`myshared`。 当使用`mystatic`时,由于它与静态库`libtemp.a`进行了链接,即使删除`libtemp.a`,`...
压缩包内包含两个文件夹curl_静态库和curl_动态库,内容说明如下: curl_静态库下面: bin子目录包含curl.exe及动态库libcurl.dll include子目录包含头文件 lib子目录包含动态库的导入库文件libcurl.lib curl_...
程序员的自我修养—链接、装载与库.pdf这本书详细介绍了这些概念和技术,包括链接器的工作原理、装载器的行为、静态库与动态库的创建和使用,以及解决链接和装载问题的方法。通过深入理解这些内容,开发者可以更有效...
本篇文章将深入探讨如何在Android Studio中利用CMake链接静态库和动态库,以实现跨平台的原生代码编译。 首先,了解静态库和动态库的基本概念。静态库(如.a或.lib文件)在编译时会将库代码直接嵌入到可执行文件中...
库通常分为两种主要类型:静态库和动态库(共享库)。这两种库各有优缺点,适用于不同的场景。 静态库,通常以.lib文件格式存在,其特点是将库中的函数和数据直接嵌入到最终的可执行文件中。这意味着当编译程序时,...
本文将深入探讨如何在JNI中引用第三方的静态库(.a)和动态库(.so),并基于提供的"Jni测试引用第三方静态库和动态库demo"进行分析。 首先,我们需要了解静态库和动态库的区别。静态库是直接链接到目标程序中的,...
Linux 下 g++ 编译与使用静态库和动态库 Linux 下 g++ 编译与使用静态库和动态库是 Linux 开发中一个非常重要的知识点。静态库(*.a)和动态库(*.so)是 Linux 下两种常用的库文件类型,了解它们的生成和使用方法...
静态库可以将所有依赖一次性链接到应用中,而动态库则是在运行时加载。这两种方式各有优缺点,开发者需要根据项目需求选择合适的集成方式。 3. **静态库与动态库的区别** - 静态库:将库代码合并到可执行文件中,...
最后,链接`main.c`与相应的库文件,观察程序的运行结果,从而深入理解静态库和动态库的工作原理及其在实际编程中的应用。 总之,无论是静态库还是动态库,它们都是Linux系统中不可或缺的一部分,深刻理解它们的...
为了编写、测试和优化C++代码,我们需要掌握如何使用编译器和调试器,以及理解静态库和动态库的概念及用法。下面我们将详细探讨这些主题。 1. **C++编译器** C++编译器是将源代码(.cpp文件)转换为可执行文件的...
动态库和静态库是两种不同的库文件形式,动态库在运行时被加载到内存中,而静态库在编译时直接链接到目标代码中。 描述进一步说明这些库是由VS2015(Visual Studio 2015)编译的,并且是v1.6.1版本。这意味着库文件...