静态库和动态库使用详解

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此好用的轮子大家一起用。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:

静态库.a(win 系统下是lib)和动态库.so(win 系统下是.dll)。

1.静态库和动态库的特点

静态库:编译过程中已经被载入可执行程序,因此程序体积较大。

共享库:可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此程序体积较小

针对2者的特点可以发现一些问题

静态库:方便移植,运行程序不需要库的存在,程序已经包含库了,库发生任何改变都要重新编译程序

动态库:不利于移植,运行程序需要库的存在,多个程序共享同一个动态库(不同虚拟地址映射同一物理地址),利于升级程序

命名特点:Linux:lib$(libname).a, lib$(libname).so,  链接 -l$(libname), Windows: $(libname).lib, l$(libname).dll

2.静态库和动态库制作

静态库

gcc -c math.c          --------------编译生成.o,只会检查语法错误,对于只有声明,没有定义也可以执行成功

ar crs libmath.a math.o           --------利用math.o生成libmath.a库,ar命令+命令参数

动态库

gcc -c -fPIC  math.c                       ----------fPIC生成地址无关程序,为了能够在多个应用程序共享,最好不要-fPIC移到下面

gcc -shared -o libmath.so math.o  ------ -shared生成动态库,

上面是Linux环境下的制作方法,Windows下,可以利用VS IDE(vs2010举例)。

新建项目--->创建win32程序-->选择生成dll/静态库,并打勾空项目

实际在项目-->属性-->常规-->配置类型(lib、dll、exe这些可选),并且注意这些库默认是32位的,还可以配置生成64位

3.库的路径设置

Linux的默认库路径

/lib、/usr/lib、/usr/local/lib(这个路径一般放第3方库)

如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:

编辑/etc/ld.so.conf文件,加入库文件所在目录的路径或者在ld.so.conf.d下新建一个.conf文件

运行ldconfig ,该命令会重建/etc/ld.so.cache文件

静态库连接时路径:对于自己写和不常用一般不会放在链接库的默认路径下,只会放在自己创建的目录下,链接的时候用-L来指定需要链接的库的路径;对于系统标准库和常用的第3方库,一般会放在默认路径下,因为在装第3方库的时候,会把库和库的头文件放在相应的库的默认路径下

动态库链接以及运行时路径:除了上面的设置以外,还有一种临时简单的设置,通过LD_LIBRARY_PATH制定。例如LD_LIBRARY_PATH = /share/lib ./test ,指定运行test程序的库路径

Windows下不是很熟悉,对于静态库来说,只需要在IDE中设置好链接时的输入和目录就行,对于动态库没有什么研究,主要是Windows的动态库和我认知不太一样,生成动态库的时候,也有链接输入选项,而且还必须输入这个动态库依赖的静态库,这点和Linux不同,动态库的路径设置没有研究

4.库的使用

库的使用好像很简单,那么不知道你们思考过这样的问题吗, 1个进程中静态库和动态库的混合使用;1个进程中动态库和静态库都要依赖,且动态库依赖于另一个静态库;2个进程中依赖的动态库都依赖某一个静态库;

这些问题让我很迷惑,说到底动态库的原理没有理解透,我的原则是,最好不要出现1个进程中动态库和静态库都要依赖,且动态库依赖于另一个静态库,这有悖于程序设计理念(模块化,抽象,去耦合),2个动态库都依赖同一个静态库,这个问题还不太清楚,我认为是没有问题的,现在想想动态库的实现真的很复杂

库的使用举例:

gcc main.c -o main -L . -lmath -lutil               ----->默认情况下优先链接动态库,-L .指定库路径为当前目录, -I指定库名

gcc main.c -o main_all_static -L . -static -lmath -lutil     ------->所有链接的库都为静态库包括标准c库,所以生成的文件很大

gcc main.c -o main_libm_sou -L . Wl,-Bstatic -lmath  Wl,-Bdynamic -lutil   ----->链接的math为静态库,其余为动态库

gcc main.c -o main_libu_som -L . Wl,-Bstatic -lutil  Wl,-Bdynamic -lmath   ----->链接的util为静态库,其余为动态库

-Wl告诉链接器接下来,链接那种库

上面的这些编译,运行都没问题,但是并不代表在多个进程都要使用上面2个库(util为动态库,math为静态库)的时候没问题,因为util要依赖math

静态链接和动态链接混用可以避免一些兼容问题

链接静态库更好的办法是-l:libxxx.a

有些链接器,链接静态库的时候,链接顺序也会导致链接错误,加入a依赖b,那么a应该放前面,b放后面

5.进阶知识

-fPIC的思考:不会使用绝对地址,只会使用相对地址,

-fPIC使.so文件的代码段变为真正意义上的共享

如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于 这个.so文件代码段和数据段内存映射的位置.

non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。

比如一条 access global data 的指令,

non-PIC 的形势是:ld r3, var1

PIC 的形式则是:ld r3, var1-offset@GOT,意思是从 GOT 表的 index 为 var1-offset 的地方处

指示的地址处装载一个值,即 var1-offset@GOT 处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,

是由 dynamic-loader(ld-linux.so) 填进去的。

再比如 jump label 指令

non-PIC 的形势是:jump printf ,意思是调用 printf。

PIC 的形式则是:jump printf-offset@GOT,意思是跳到 GOT 表的 index 为 printf-offset 的地方处

指示的地址去执行,这个地址处的代码摆放在 .plt section,每个外部函数对应一段这样的代码,其功能是呼叫

dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方,

同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。

GOT 是 data section, 是一个 table, 除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;

PLT 是 text section, 是一段一段的 code,执行中不需要修改。

查看执行文件和动态库的动态库依赖的工具 :ldd file_name

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值