地址无关代码,在64位下编译动态库的时候,经常会遇到下面的错误
/usr/bin/ld: /tmp/ccQ1dkqh.o: relocation R_X86_64_32 against `a
local symbol can not be used when making a shared object;
recompile with -fPIC
提示说需要-fPIC编译,然后在链接动态库的地方加上-fPIC的参数编译结果还是报错,需要把共享库所用到的所有静态库都采用-fPIC编译一边才可以成功的在64位环境下编译出动态库。
这里的-fPIC指的是地址无关代码
这里首先先说明一下装载时重定位的问题,一个程序如果没有用到任何动态库,那么由于已经知道了所有的代码,那么装载器在把程序载入内存的过程中就可
以直接安装静态库在链接的时候定好的代码段位置直接加载进内存中的对应位置就可以了。但是在面对动态的库的时候
,这种方式就不行了。假设需要载入共享库A,但是在编译链接的时候使用的共享库和最后运行的不一定是同一个库,在编译期就没办法知道具体的库长度,在链接
的时候就没办法确定它或者其他动态库的具体位置。另一个方面动态库中也会用到一些全局的符号,这些符号可能是来自其他的动态库,这在编译器是没办法假设的
(如果可以假设那就全是静态库了)
基于上面的原因,就要求在载入动态库的时候对于使用到的符号地址实现重定位。在实现上在编译链接的时候不做重定位操作,地址都采用相对地址,一但到了需要载入的时候,根据相对地址的偏移计算出最后的绝对地址载入内存中。
但是这种采用装载时重定位的方式存在一个问题就是相同的库代码(不包括数据部分)不能在多个进程间共享(每个代码都放到了它自己的进程空间中),这个失去了动态库节省内存的优势。
为了解决这个问题,ELF中的做法是在数据段中建立一个指向那些需要被使用(内部的位置无关简单采用相对地址访问就可以实现)的地址列表(也被称为全局偏移表,Global
offset table, GOT). 可以通过GOT相对应的位置进行间接引用.
对于我们的32位环境来说, 编译时是否加上-fPIC, 都不会对链接产生影响,
只是一份代码的在内存中有几个副本的问题(而且对于静态库而言结果都是一样的).但在64位的环境下装载时重定位的方式存在一个问题就是在我们的64位环
境下用来进行位置偏移定位的cpu指令只支持32位的偏移,
但实际中位置的偏移是完全可能超过64位的,所以在这种情况下编译器要求用户必须采用fPIC的方式进行编译的程序才可以在共享库中使用
从理论上来说-fPIC由于多一次内存取址的调用,在性能上会有所损失.不过从目前的一些测试中还无法明显的看出加上-fPIC后对库的性能有多大的损失,这个可能和我们现在使用的机器缓存以及大量寄存器的存在相关.
小提示:
-fPIC与-fpic
上面的介绍可以看到,gcc要使用地址无关代码加上-fPIC即可,但是在gcc的手册中我们可以看到一个-fpic(区别在一个大写一个小写)的参数,
从功能上来说它们都是一样的。-fpic在一些特定的环境中(包括硬件环境)可以有针对性的进行优化,产生更小更快的代码,
但是由于受到平台的限制,像我们的编译环境,开发环境,运行环境都不完全统一的情况下面使用fpic有一定未知的风险,所有决大多数情况下我们使用
-fPIC来产生地址无关代码。共享内存效率
共享内存在只读的情况下性能和读普通内存是一样的(如果不算第一载入的消耗),而且由于是多个进程共享对cpu
cache还显的相对友好。
同时存在静态库和动态库
前面提到编译动态库的时候有提到编译动态库可以像编译静态库那样采用-Lpath -lxx的方式进行,
但这里存在一个问题,如果在path目录下既有动态库又有静态库的时候的行为又是什么样地? 事实上在这种情下,
链接器优先选择采用动态库的方式进行编译.比如在同一目录下存在 libx.a 和 libx.so,
那么在链接的时候会优先选择libx.so进行链接. 这也是为什么在com组维护的第三方库(third,
third-64)中绝大多数库的产出物中只有.a的存在, 主要就是为了避免在默认情况下使用到.so的库,
导致在上