天天看点

linux链接库简介

linux链接库简介

       最近要考linux编程,发现linux库这个东西,怎么也无法理解,在网上兜了一大圈,看了很多东西,才算模模糊糊搞懂了,下面把自己总结的东西贴上来与大家共享:

        在linux系统中,可将多个目标文件打包成库文件,以便在编程时随时调用,而不必重新编写或定义,这种包称为库函数。库文件是一些预先编译好的函数的集合,那些函数都是按照可再使用的原则编写的。它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(curses库)等。

        1.基础知识:

        linux下GCC在编译程序时要经历预处理,编译,汇编和连接四个阶段。

预处理阶段,主要处理#include和#define,它把#include包含进来的.h 文件插入到#include所在的位置,生成.i文件;

        编译阶段,是最重要的阶段,在这个阶段GCC首先检查语法然后把文件转换成汇编程序,生成.s文件;上面这两步的输出文件都是文本文件,我们可以用诸如cat的文本处理等命令阅读这些输出文件;

        汇编阶段,把*.s文件翻译成二进制机器指令文件*.o,需要反汇编工具如GDB的帮助才能读懂它;

        连接阶段,gcc在这个阶段把所有的*.o文件连接成一个可执行文件,库文件的连接也在这步完成。

GCC编译过程:

预处理-----> 编译 ----> 汇编 ----> 链接

1)预处理(Pre-processing)

在该阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来,用户可以使用gcc的选项”-E”进行查看。

用法:#gcc -E hello.c -o hello.i

作用:将hello.c预处理输出hello.i文件。

2)编译阶段(Compiling)

在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。

用法:[root]# gcc -S hello.i -o hello.s

作用:将预处理输出文件hello.i汇编成hello.s文件。

3)汇编阶段(Assembling)

汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码.

用法:[root]# gcc -c hello.s -o hello.o

作用:将汇编输出文件test.s编译输出test.o文件。

4)链接阶段(Link)

在成功编译之后,就进入了链接阶段。

无选项链接

用法:[root]# gcc hello.o -o hello

作用:将编译输出文件hello.o链接成最终可执行文件hello。

        2. 标准库与非标准库

        库函数可分为标准与非标准(自定义)库两大类。

        (1)标准库文件是公用的,系统中的任何用户都可以利用这些库函数,一般保存在/lib或者/usr/lib目录里,并以头文件的方式提供包含调用。编译时要告诉C语言编译器(更确切地说是链接程序)应去查找哪些库文件,默认情况下,它只会查找标准库文件。

        标准库在使用时,gcc等编译程序能够自动连接,所以在只需要包含其定义的头文件即可,如libc.a。libc.a为标准C函数库,它包含了诸如内存管理或者输入输出操作的基本函数。

        (2)非标准库在连接时,必须加上-lname(name为去掉lib和尾部的.a或.so后的库名)参数;非标准库可以放在任意目录中,一般放于当前目录中,但当放置在非系统默认搜索路径中时,需要用-Ldir(dir为路径名称)指定搜索路径。

        例如,数学函数并不是C标准库的组成部分,他们是由数学库/usr/lib/libm.a所定义的,因而在使用该库中的数学函数时,除了用#include<math.h>将头文件/usr/include/math.h加入到程序文件中,还要明确的用gcc的-lm选项来连接这个库(数学库libm.a放置在系统默认的库搜索路径/usr/lib中,因而不需要-Ldir参数)。

        3. 静态库与共享库

        函数库一般分为静态和共享(也称动态库)两种格式,库文件必须遵守一定的命名规则,库文件的名字永远以lib打头,随后是说明函数库情况的部分(比如用c表示这是一个 C语言库,而m表示这是一个数学运算库等)。文件名的最后部分以一个句点(.)开始,然后给出这个库文件的类型,.a为静态库文件,.so和.sa为共享型库文件。无论动态库还是静态库都需要用.o文件来生成。

        一般 Linux 系统把 /lib 和 /usr/lib 两个目录作为默认的库(动态库或静态库)搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到库的搜索路径之中(如修改环境变量),也可以在程序连接时,通过-L参数来指定。

        但由于在运行时,程序还需要连接动态库,因而,对于动态库,最好还是将库的位置添加到库的搜索路径之中或是把所用的库拷贝到系统默认的库搜索目录中。静态库则无此限制。

        (1)静态库

$ gcc -c hello.c -o hello.o  

$ ar cqs libhello.a hello.o  

链接,这里指定了静态库的位置 //-static参数

$ gcc -static -libhello test.c -o test

它等价于:

$ gcc test.o libhello.a -o test

看引用库情况。

$ ldd test    

      静态库也叫做档案(archive),文件名按惯例都以.a结尾,比如C语言标准库/usr/lib/libc.a,X11库/usr/X11R6/lib/libX11.a等。它所包含的成员是若干.o文件,静态库中的各个成员(.o文件)没有特殊的存在格式,仅仅是一个.o文件的集合。在连接时,静态库的文件代码会被拷贝到可执行文件中,所以静态连接的可执行文件一般比较大一些。

        在Linux(Unix)中使用工具“ar”对静态库进行维护管理,创建静态库用ar命令,在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

         # ar -rc libmyhello.a hello.o

        静态库的缺点是,如果我们在同一时间运行多个程序而它们又都使用着来自同一个函数库里的函数时,内存里就会有许多份同一函数的备份,在程序文件本身也有许多份同样的备份。这会消耗大量宝贵的内存和硬盘空间。

        (2)共享库

$ gcc -shared -Wall -fPIC hello.o -o libhello.so 

-shared:该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。

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

-fPIC : If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table.  This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.

        也称动态库,许多UNIX系统支持共享库,它同时克服了静态库在内存和硬盘方面的无谓消耗。链接时,动态库的代码不会被加入可执行文件中,而是在程序被执行的时候加载。

        共享库的默认存放位置和静态库是一样的,但有着不同的文件后缀。在一个典型的 Linux系统上,C语言标准库的共享版本是/usr/lib/libc.so N,其中的N是主版本号。当静态库和动态库同名时,gcc命令将优先使用动态库。

        可用gcc来创建动态库,在系统提示符下键入以下命令得到动态库文件libmyhello.so。

        # gcc -shared -fPCI -o libmyhello.so hello.o

        4. 对比

        静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在。

        假如程式是在编译时加载库文档的,就是使用了静态库。假如是在运行时加载目标代码,就成为动态库。换句话说,假如是使用静态库,则静态库代码在编译时就拷贝到了程式的代码段,程式的体积会膨胀。假如使用动态库,则程式中只保留库文档的名字和函数名,在运行时去查找库文档和函数体,程式的体积基本变化不大。

        静态库的原则是“以空间换时间”,增加程式体积,减少运行时间;动态库则是“以时间换空间”,增加了运行时间,但减少了程式本身的体积。

        5. 总结

        标准静态库或共享库,路径和库名都不需显示指定;

        非标准静态库,必须显示指定库名,不在默认路径中时需指定路径;

        非标准共享库,编译时:必须指定库名,不在默认路径中需指定路径名,

        运行时,不在默认路径中时需要将库位置添加到库搜索路径中或拷贝库到默认路径中。

链接方法I

拷贝到系统库里再链接,让gcc自己查找

$ sudo cp libhello.so /usr/lib  

gcc test.o -lhello -o test 

这里我们可以看到了 -lhello 选项,-l[lib_name] 指定库名,他会主动搜索lib[lib_name].so 。

这个搜索的路径可以通过 gcc --print-search-dirs来查找。

链接方法II

手动指定库路径

$ cc -o test test.o -lhello -B .

这里的-B 选项就添加当前目录到gcc搜索的路径之中。这样链接在执行时候仍旧需要指定库路径(链接和执行是分开的)。

也可以直接用-L指定动态链接库所在的目录

$ cc -o test test.o -lhello -L .

需要添加系统变量 LD_LIBRARY_PATH :

$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

/etc/ld.so.conf 是系统对动态链接库进行查找的路径配置文件,是系统链接工具/usr/bin/ld查找动态链接库的地图。

因此,也可以:

pwd >> /etc/ld.so.conf   

ldconfig

这个时候再来检测一下test程序的库链接状况

$ ldd test  

比静态链接的程序多了一个 libhello.so,这是静态与动态的最大区别,静态情况下,他把库直接加载到程序里,而在动态链接的时候,他只是保留接口,将动态库与程序代码独立。这样就可以提高代码的可复用度,和降低程序的耦合度。

Linux操作系统上面的动态共享库大致分为三类:

1、操作系统级别的共享库和基础的系统工具库

比方说libc.so, libz.so, libpthread.so等等,这些系统库会被放在/lib和/usr/lib目录下面,如果是64位操作系统,还会有/lib64和/usr /lib64目录。如果操作系统带有图形界面,那么还会有/usr/X11R6/lib目录,如果是64位操作系统,还有/usr/X11R6 /lib64目录。此外还可能有其他特定Linux版本的系统库目录。这些系统库文件的完整和版本的正确,确保了Linux上面各种程序能够正常的运行。

2、应用程序级别的系统共享库

并非操作系统自带,但是可能被很多应用程序所共享的库,一般会被放在/usr/local/lib和/usr/local/lib64这两个目录下面。很多你自行编译安装的程序都会在编译的时候自动把/usr/local/lib加入gcc的-L参数,而在运行的时候自动到/usr/local /lib下面去寻找共享库。

以上两类的动态共享库,应用程序会自动寻找到他们,以上这些目录默认就被加入到动态链接程序的搜索路径里。Linux的系统共享库搜索路径定义在/etc/ld.so.conf这个配置文件里面。这个文件的内容格式大致如下:

/usr/X11R6/lib64

/usr/X11R6/lib

/usr/local/lib

/lib64

/lib

/usr/lib64

/usr/lib

/usr/local/lib64

/usr/local/ImageMagick/lib

假设我们自己编译安装的ImageMagick图形库在/usr/local/ImageMagick目录下面,并且希望其他应用程序都可以使用 ImageMagick的动态共享库,那么我们只需要把/usr/local/ImageMagick/lib目录加入/etc/ld.so.conf文件里面,然后执行:ldconfig 命令即可。

ldcofig将搜索以上所有的目录,为共享库建立一个缓存文件/etc/ld.so.cache。为了确认ldconfig已经搜索到ImageMagick的库,我们可以用上面介绍的strings命令从ld.so.cache里面抽取文本信息来检查一下:

strings /etc/ld.so.cache | grep ImageMagick 

3、应用程序独享的动态共享库

有很多共享库只被特定的应用程序使用,那么就没有必要加入系统库路径,以免应用程序的共享库之间发生版本冲突。因此Linux还可以通过设置环境变量LD_LIBRARY_PATH来临时指定应用程序的共享库搜索路径,就像我们上面举的那个例子一样,我们可以在应用程序的启动脚本里面预先设置 LD_LIBRARY_PATH,指定本应用程序附加的共享库搜索路径,从而让应用程序找到它。

具体如下:

(1)新建自己的头文件路径/home/user/workspace/include和库文件路径/home/user/workspace/lib,这两个目录用来存放我自己编写的头文件和库文件;

(2)接下来写个头文件和库文件作测试,在/home/user/workspace/include下新建文件test.h,在/home/user/workspace/lib下新建test.c,test.h中是一些函数原型,test.c是函数的实现;

(3)通过命令gcc -fPIC -shared -o libtest.so test.c在/home/user/workspace/lib生成了一个动态链接库文件libtest.so;

(4)现在把库文件路径添加进.bash_profile文件,添加内容如下:

# my code

C_INCLUDE_PATH=/home/cheney/workspace/include

export C_INCLUDE_PATH

LD_LIBRARY_PATH=/home/cheney/workspace/lib

export LD_LIBRARY_PATH

然后通过source .bash_profile把.bash_profile文件即时更新了

继续阅读