工程中编写自己的makefile---4 库文件

1       库文件

编写一个C语言程序的时候,经常会遇到好多重复或常用的部分,如果每次都重新编写固然是可以的,不过那样会大大降低工作效率,并且影响代码的可读性,更不利于后期的代码维护。我们可以把他们制作成相应的功能函数,使用时直接调用就会很方便,还可以进行后期的功能升级。

库本质上来说是一种可执行的二进制代码(但不可以独立执行),可以被操作系统载入内存执行

库通俗的说就是把这些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用。库是别人写好的现有的,成熟的,可以复用的代码,我们只需要知道其接口如何定义,便可以自如使用。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。比如我们常使用的printf函数,就是c标准库提供的函数。我们在使用时只需要包含相应的头文件就可以使用(非静态编译还要有相应的库文件)。而不用关心printf函数具体是如何实现的,这样就大大提高了程序员编写代码的效率。

从使用方法上分库大体上可以分为两类:静态库和共享库(动态库)。

1,在windows中静态库是以.lib为后缀的文件,共享库是以 .dll为后缀的文件。

2,在Linux  中静态库是以.a为后缀的文件,共享库是以.so为后缀的文件。

1.1.1        gcc关于库的参数

1,-shared

该选项指定生成动态连接库

2,-fPIC

表示编译为位置独立(地址无关)的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的

3,-L

指定链接库的路径,-L. 表示要连接的库在当前目录中

4,-ltest

指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

5,-Wl,-rpath

记录以来so文件的路径信息

6,LD_LIBRARY_PATH

这个环境变量指示动态连接器可以装载动态库的路径

注意!!!如何指定自己的动态库路径,假设想增加路径/usr/local/lib

可以在/etc/profile文件中增加以下代码来实现:

export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"

这样,在运行程序(编译程序是可能也可以由改变量指定,没有尝试过,一般是在makefile直接制定库文件目录)是就不会提示找不到动态链接库了,此方法永久增加库路径

运行程序默认实在 /lib/,/usr/lib目录找到库文件,也可以把用户的动态库文件放在以上两个目录当中;

7,LIBRARY_PATH

静态链接库文件搜索路径,同上

1.1.2        库文件生成方式

以linux下的静态库和动态库为例我们看一下生成方式

1,静态库

a,将源文件编译成目标文件:gcc -c a.c b.c

b,生成静态库:ar–rc libname.a a.o b.o

2,共享库

a,将源文件编译成目标文件:gcc -c a.c b.c

b,生成共享库:gcc -fPIC -shared -o libname.so a.o b.o

由此可见静态库和动态库都是对目标文件的处理,也可以说库文件已经是机器码文件了;

1.1.3        库文件命名规范

在linux下,库文件一般放在/usr/lib和/lib下,

静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称

动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号

为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀;

例如:libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。

ln -s libhello.so.1.0libhello.so.1

ln -s libhello.so.1libhello.so

1.1.4        库文件加载过程

注意!!!静态库和动态链接库同时存在时,gcc/g++默认链接的是动态库

 

1,只静态库的链接方法

gcc -o $(TARGET) -L. -llibnamemain.c -static  (默认库在当前文件夹)

2,只共享库的链接方法

gcc -o $(TARGET) -L. -llibnamemain.c          (默认库在当前文件夹)

3, 当对动态库与静态库混合连接的时候,需要使用需要作用-Wl的方式

 

3.1指定让gcc/g++链接静态库

-Wl,-Bstatic -llibname

使用:

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bstatic -llibname -Wl,-Bstatic -llibname-Wl,-Bdynamic

3.2指定让gcc/g++链接动态库

-Wl,-Bdynamic -llibname

使用:

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bdynamic -llibname -Wl,-Bdynamic –llibname

注意!!!上面静态和动态连接中代码的区别,红色加粗字体

系统的运行库默认使用动态连接的方式,所以当动态库在静态库前面连接时,必须在命令行最后使用动态连接的命令才能正常连接,最后的-Wl,-Bdynamic表示将缺省库链接模式恢复成动态链接。

 

3.3指定让gcc/g++混合链接动静态库

$(CC) -o $(TARGET)$(OBJS) -L. $(CFLAGS) -Wl,-Bstatic -llibname -Wl,-Bdynamic –llibname

 

当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了,这样运行起来相对就快些。不过会有个缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.

与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时,那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用,也同时节约了内存。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些;

总的来说静态库是牺牲了空间效率,换取了时间效率,共享库是牺牲了时间效率换取了空间效率,没有好与坏的区别,只看具体需要了。

共享库(动态库)的好处是:不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例

为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式

另外,一个程序编好后,有时需要做一些修改和优化,如果我们要修改的刚好是库函数的话,在接口不变的前提下,使用共享库的程序只需要将共享库重新编译就可以了,而使用静态库的程序则需要将静态库重新编译好后,将程序再重新编译一便。

1.1.5        ldd工具

ldd可以查看可执行程序依赖那些动态库或着动态库依赖于那些动态库

1,可执行程序依赖那些动态库

[root@9527 Project]# ldd xxserver

linux-gate.so.1=>  (0x00110000)

libpthread.so.0 =>/lib/libpthread.so.0 (0x004f4000)

libc.so.6 =>/lib/libc.so.6 (0x0034c000)

/lib/ld-linux.so.2(0x00327000)

2,动态库依赖于那些动态库

[root@9527 objs]# ldd libadd.so

    linux-gate.so.1=>  (0x00110000)

    libc.so.6 =>/lib/libc.so.6 (0x00113000)

    /lib/ld-linux.so.2(0x00327000)

1.1.6        nm工具

使用nm工具,查看静态库和动态库中有那些函数名;

1.1.7        ar工具

可以使用 ar -t libname.a 来查看一个静态库由那些.o文件构成

[root@9527 objs]# ar -t libadd.a

add_float.o

add_int.o

1.1.8        如何查看动态库和静态库是32位,还是64位下的库

如果是动态库,可以使用file *.so;

如果是静态哭,可以使用objdump -x *.a(-f可能看到更简单的信息)

 

发布了51 篇原创文章 · 获赞 106 · 访问量 33万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章