首页UC › Linux平台静态库、动态库的一些笔记

Linux平台静态库、动态库的一些笔记

先声明几点:

1、操作系统:linux(fc9)、编译器:gcc-4.3.0、编辑器:包括但不限于emacs、vim。这些无理由也不应造成限制。

2、生成的可执行文件名称比较有规律,仅仅是为了演示的方便。比如使用静态库生成的是foo,不同的生成方法得到的可执行文件可能会是foo-a、foo-b……,而使用动态库生成的是foobar,可能会是foobar-a、foobar-b……,等等。

3、木草山人正在看的那本《程序员的自我修养——链接、装载与库》只看了前面部分,而且还只看第一遍,很多知识不牢固。因此不会深入讲述原理性的东西,比如静态库与动态库的优点与缺点,它们是怎么加载的。此外也不涉及共享库版本、兼容性以及SO-NAME,等等——很多时候,我们不必要追根问底,特别是在计算机领域中。

4、示例程序仅为演示程序,不代表实践中的操作、编写方法。——比如使用编译器的-g、-O选项,等等。但却具有实践指导意义。

5、山人尽量保证文章属实,但限于自身见识和水平,即使在自己的机器上亲自实践一次,辛辛苦苦的粘贴运行结果,但不敢保证100%正确。大家体谅一下。

背景代码:

1、我们的库的头文件lib.h

2、我们的库文件lib.c

3、我们的测试文件man.c

程序很简单,就是在库文件中的函数foobar中输出一行字符串以及一个传递给它的参数。在主函数中同样输出一行字符串。

下文如果提到源代码名称,都是指上面的那些东东,这点就不再说明了。至于为何传递的参数的250呢?这是山人自己写的,你可以修改成其它值。

我们的静态库文件名称:libfoo.a

我们的动态库文件名称:libfoobar.so

同样的函数,使用库名称不同,是因为在同一目录下存在相同名称的库时,gcc会优先考虑动态库的。

实践

1、静态库的生成

Linux下编译成静态库很简单。使用如下命令

其中第一行命令是生成lib.o目标文件。第二行使用ar命令生成libfoo.a静态库。注意,这里我们在文件名称上稍稍区别一下静态库与动态库。ar命令是创建、修改文档(archive),或者从文档中解压,c代表创建,r表示插入(带替换功能)。这些解释是从man ar中翻译过来的,详细请参考man ar或者google。

Linux平台的库文件名遵循一定的约定,它们以lib开头,以.a或.so结尾,中间的就是库的名称,在使用gcc编译时直接使用-l选项指定库名称即可。比如这里的libfoo.a以及后面将会生成的libfoobar.so。

我们来看一下libfoo.a都包含了哪些符号

其实,它与nm lib.o输出效果是一样的,这可以从生成静态库的命令看出一点关系。注意,那个lib.o并不是山人写错,它是显示出来的信息,并没有修改过。

2、使用静态库

我们使用这个静态库来编译测试程序

另外一个方法

-L选项表示查找库的目录,这里的-L.意思是在当前目录查找,-l选项是指定需要的库文件,上面提到一点,就是其后直接跟“库名称”,这里的libfoo.a遵循了Linux库命名的约定,因而直接使用-lfoo来指定。

上面两种方法的效果是一样的。

3、动态库的生成

动态库的生成同样简单,命令如下

其中的lib.o也可以用lib.c代替,结果是一样的。

-f后面的PIC表示生成的库中符号是与位置无关的(PIC就是Position Independent Code),关于PIC,可以参考这篇文章

Introduction to Position Independent Code

-shared表示共享,共享库后缀名.so可以认为是shared object的简称。

4、使用动态库

同样地,使用动态库也有许多种方法。

比如

还有一种

编译没出现出错,但运行出错了,看提示信息,说不能加载libfoobar.so,因为没有那个目录。

原来,我们自己那个共享库不在系统默认的共享库路径中。有几种方法,一是将当前目录添加到共享库搜索目录;二是将我们自己的共享库放到系统默认的库目录中;三是暂时指定共享库的路径。显然,第一种方法和第二种不太可取,——就像将我们自己编写的头文件放到系统默认头文件目录那样。当然,在我们设计嵌入式根文件系统时,我们是可以将自定义的库放到系统目录中的。这里,我们使用第三种方法。执行以下命令

可以看到,程序能运行了。我们使用export临时指定当前目录为共享库目录。此时,我们使用ldd命令看一下该可执行文件共享库的信息

其中第二行libfoobar.so => ./libfoobar.so (0x004ec000)表明了我们自己的共享库就在当前目录下。

然而,当我们切换到另一个终端,或者退出系统重新登陆,再次执行这个命令,得到如下信息

我们清楚地看到libfoobar.so => not found,没有找到这个共享库。因为export仅对当前会话当前终端有效。将这个目录添加到系统的共享库目录是一种治根治标的方法,不过,我们需要根据实际情况作出选择。好,下面试一下第二种,也就是将我们的库文件放到系统库目录中,系统共享库搜索路径是由/etc/ld.so.conf这个文件指定的,不同版本的系统其内容亦不同。其实里面无非就是库的目录而已。这里,我们放到/lib目录下,之后,要记得使用ldconfig刷新共享库缓存/etc/ld.so.cache,注意需要使用root权限才能操作成功。

下面查看一下操作是否成功了

一切正常,已经找到libfoobar.so文件了。

(这里插一下题外话,很多同志在编译跟qt相关的程序(或者编译qt)时会遇到各种莫名其妙的错误,其中不乏库版本不对应这种错误,我们可以大胆猜测,这跟这里提及到的ld.so.conf有关,因此,我们遇到这种错误时,不妨先查看ld.so.conf文件是否是我们需要的路径)

下面是编译、运行的过程。注意,我们的共享库不是系统默认的库,需要使用-l来指定共享库名称。其它如线程库也不是Linux默认的,需要用-lpthread指定。

在《程序员的自我修养》中,还有一种方法,如下

我们的共享库由-rpath来指定(注意-rpath空格后面那个点,表示当前目录),这种效果跟第一种方法一样。

另外,如果-rpath指定的路径是绝对路径的话,那么生成的可执行文件放到任意目录中执行,都是可以的。CU上有帖子说-rpath指定的目录是已经写入可执行文件里面了,加载时,是使用-rpath指定的目录来加载共享库的。因此,如果使用当前目录的话,只要将共享库和可执行文件放到一起,就可以执行。经山人测试,这种说法是正确的。

我们使用ldd看一下这两种方法生成的可执行文件的共享库信息

但是,它们还是有区别的,具体的在此处不再深入了。

题外实践(下面内容纯粹是没事找事做,感兴趣的可以看看)

好了,实例显示完毕,下面再研究一下其它方面的东西。

上面所有例子都是动态链接的,现在我们使用静态链接

我们在前面静态库编译时添加了-static选项。

我们使用ldd命令再次查看一下静态库生成的可执行文件的信息

细心的你应该注意到,我们生成的可执行文件中,凡是foo都是由静态库libfoo.a生成的,凡是bar都是libfoobar.so生成的。大家能体会到山人的良苦用心吧?

前面两个是动态链接的,因而能查看与之相关的动态库信息,而最后一行,就是静态链接的foo-s,就显示不出来了。

注意,这里没有提示关于libfoo.a的信息(似乎是废话,人家ldd都表明是动态的了,你非要显示静态的!开个玩笑,呵呵)。

我们再看一下它们占用的体积有多大

最后一行是我们静态链接得到的可执行文件,可以看到,它灰常的大,是动态链接的100多倍!

那么,我们能不能静态链接我们的动态库呢?

这是libfoobar.so放在当前目录的情况。我们将libfoobar.so复制一份到/lib目录中了,再执行一次

也不行。算了,这个不再研究了。

我们再来研究一下动态库与静态库的符号。

查看任意一个使用动态库链接库生成的可执行文件,可以发现里面的foobar是未定义的(未定义符号为U,倒数第4行)。

而使用静态库生成的可执行文件中的foobar是全局TEXT符号(倒数第5行,符号为T,表示在这个模块中已经定义了的)。

我们再来看一下静态链接后的可执行文件(注意,该文件符号很多,这里删除了大量符号)

细心的你同样会注意到,在foo(动态链接)可执行文件中,printf函数是未定义的,而foo-s(静态链接)可执行文件中,printf却是已经定义了的。前者,需要动态地加载一些必要的库函数,因为这些函数不在那个可执行文件中,而静态链接却包含了许多函数,因此,不需要再加载了。知道这点后,我们在看许多资料,看到说静态链接占用很多体积,如何不好……时,应该有点感性认识了吧?

我们也可以再做个实验来说明这一点。

我们在lib.c中再添加一个不调用它的函数:Hello。而我们的测试程序main.c没有作修改。

现在lib.c应该是这个样子的

这个函数中的字符串是可以使用strings命令读到的。

同样地,我们再次生成一个静态库和动态库,这里就省略了步骤了。

我们来看一下使用静态库生成的可执行文件

可以看到,我们没有调用hello这个函数,但它依然是T!至于这个文件中其它的未定义的符号,是因为foo-b始终还是动态链接的!这点务必注意。

如果我们查看那几个foo文件,会发现,这个foo-b稍微大一点

不为什么,它已经包含了hello这个函数了。不信,再使用strings命令看一下

最后一行就是我们未调用的的那个函数打印的。

另外,我们使用同样的方法查看使用动态库生成的可执行文件,会发现,它与前面的那几个并没有发生变化。但是,libfoobar.so中也已经有了hello这个函数了(这句也是废话,大家直接无视之)。

再次声明:

山人初出茅庐,对许多专业术语使用得不是很恰当,有些理解也是牵强附会,连贯性不好。还请大家见谅并批评指正,谢谢大家!

PS:写这种文章,吃力又不讨好,很辛苦的!山人花费大量时间,不仅要在linux下测试,还需要参考很多资料,比如到javaeye、CU、CSDN等等网站上看人家以前讨论的帖子。这个过程虽然漫长,不过,乐于其中也不觉得有多么辛苦了。

发表评论