当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > 简析静态库与动态库

简析静态库与动态库 时间:2018-09-27      来源:未知

一、库的简介

当今程序员的程序开发流程与50年前对比可谓是发生了翻天覆地的变化:50年前,那些“上古时期”的大神们没有简便的可视化操作系统,没有详 尽的API文档,没有方便的面向对象语言(面向过程语言刚刚兴起),甚至连一些当今程序员认为某些“天生的”功能(例如C语言的printf函数)都 没有。50年过去了,当今的程序员们可能无法体会过去的大神们编程的艰辛,因为有一种工具包的存在,使得编程大大简化,让程序开发者更多地去 注重程序的逻辑性而不是一些“细枝末节”。这种工具包就是库。

库(library)是一种可执行代码的二进制形式,通常把一些常用的函数制作成各种函数库,然后被系统载入内存中运行。库是许多前辈大神们已 经写好的程序,程序开发者可以直接来调用这些功能程序来完成相应功能,从而简化了程序的开发工作。而且如果不同的应用程序调用同样的库,那 么内存内只需有一份该库的实例即可,节省了存储空间。库内一般都是各种标准程序、子程序、相关文件以及目录等的集合,内置一些经常用的程序 。主要有三种:

标准子程序:例如三角函数、反三角函数等

标准程序:例如解常微分方程等

服务性程序:例如输入、输出、磁盘操作、调试等。

以熟悉的C语言stdio库为例。stdio库意为标准输入输出库(standard input & output),该库内集成的是用于控制输入、输出、输出错误的相关 功能函数,例如我们熟悉的fopen()、fclose()、fread()、fwrite()、putchar()、getchar()、printf()、scanf()等函数都集成在该库内。从C89版 本开始,一般C语言编译器都会自带stdio库,只需我们在程序中包含头文件stdio.h即可调用库内的功能函数。这样就大大简化了程序的开发工作。

Linux系统下的库分为静态库与动态库两种。二者的不同点主要体现在载入时间的不同(见附图1)。静态库在程序编译时的链接阶段被链接到目标代 码中,运行程序时将不再需要静态库。编译后的可执行程序体积较大。动态库在程序编译时并不会马上链接到目标代码中,而是在执行阶段才被程序 载入,因此编译后的可执行程序体积较小,但是需要系统动态库存在。

二、静态库简介与制作

静态库在程序编译的“链接”阶段生效。在编译过程中,若需要加载静态库,则在链接阶段,编译器会拷贝一份完整的库函数代码,整合到当前正在 编译的程序中,这样在编译完成后库就被整合到了程序内部。这种加载库的方式称为“静态库”。由于静态库与程序整合在一起,因此程序体积较大 ,在程序运行时无需二次加载所需的库,不过库的更新也变得困难。而且,由于静态库是采取“拷贝”的方式来加载库,因此无法实现不同进程间的 库的共享。

那么如何制作一个静态库呢?

在Linux系统中,我们可以使用ar工具制作一个静态库。ar是类似gcc的一个GNU工具包内的工具,作用是建立、修改、提取归档文件。归档文件是包含 多个文件内容的一个大文件,被包含文件的原始内容、权限、时间戳、所有者等属性都保存于归档文件中,并且可以通过“提取”来还原该文件。

下面我将制作一个名为libmyhello.a的静态库。

(注意:在Linux系统中,库文件的文件名一般为libXXX.a或libXXX.so,其中lib表示这是一个库,.a表示静态库,.so表示动态库,XXX为库名。在 Windows系统中以不同的文件后缀名区分静态库与动态库,其中.lib文件为静态库,.dll文件为动态库。)

第一步:准备3个文件:hello.h、hello.c、test.c。其中hello.h和hello.c用于制作静态库,test.c是测试程序主函数。

第二步:将hello.c编译生成目标文件hello.o

gcchello.c -c -o hello.o

第三步:使用ar将hello.o制作成静态库

arcrslibmyhello.ahello.o

第三步的参数解释:

⒈c:表示无提示方式创建文件包

⒉r:在文件包中替代文件

⒊s:强制重新生成文件包的符号表

此时就生成了文件名为libmyhello.a(库名为myhello)的静态库。下一步就可以将该静态库链接到程序中了。

第四步:编译test.c,将刚制作的静态库加载至程序内

gcctest.c -L. -lmyhello -o hello

其中参数-L的意思是添加所需增加的库的路径,-L.表示增加库的路径为当前路径。参数-l的意思是在链接阶段寻找该库并链接至程序中。

经过以上四步,我们就成功制作了一个静态库并将它成功地添加到程序中。执行程序hello即可看到结果。

并且若我们删除库(即libmyhello.a文件),再次执行该程序仍然可以得到正确的结果。这是因为静态库在链接阶段已经和程序整合到一起,即使原 始库文件不存在,程序依然可以成功执行。

三、动态库(共享库)的简介与制作

静态库在使用过程中有许多的缺点,包括但不限于:库与程序整合到一起,这样会使得程序占用空间变大;如果库需要更新,则需要重新编译;由于 加载库是采取拷贝的方式,这样程序与程序之间没有实现库的共享……。

基于以上几点,我们发明了动态库。与静态库不同的是,动态库在链接阶段并没有真正的整合到程序内部,而是保留了库的一个“线索”,当我们执 行该程序时,程序会按照这条“线索”与当前系统的环境变量寻找库的真正所在位置并加载。这样做的好处是将库与程序人为分离,这样便于库的更 新与维护,同时多个程序间只需保留一份库的实例即可,无需拷贝库而浪费内存。不过这样做的缺点就是程序对动态库有依赖性,即程序无法脱离库 而独立运行。

(如果有玩过(尤其是盗版)游戏的同学,一定遇到过“缺少XXX.dll文件”的问题从而导致游戏无法正确运行。.dll文件即Windows系统下的动态库 文件,缺少该文件即使游戏能够正确地安装到电脑上,也会因缺少相应库文件而无法执行。)

那么如何制作一个动态库呢?

我们可以使用gcc直接制作一个自己的动态库。

第一步:需要准备3个文件:hello.h、hello.c、test.c。其中hello.h和hello.c用于制作动态库,test.c是测试程序主函数。(代码与上面相同,略 )

第二步:使用gcc编译生成动态库。

gcchello.c -fPIC -c -o hello.o

gcchello.o -shared -o libmyhello.so

(或者直接写成一步:gcchello.c -fPIC -shared -o libmyhello.so)

第二步的参数解释:

⒈ -fPIC(或-fpic):表示编译为位置独立的代码。位置独立的代码即位置无关代码,在可执行程序加载的时候可以存放在内存内的任何位置 。若不使用该选项,则编译后的代码是位置相关的代码,在可执行程序加载时仍然是通过代码拷贝的方式来满足不同的进程的需要,并没有实现真正 意义上的位置共享。

⒉ -shared:指定生成动态链接库。

此时就生成了文件名为libmyhello.so(库名为myhello)的动态库。下一步就可以将该动态库链接到程序中了。

第三步:编译test.c,将刚制作的动态库加载至程序内。

gcctest.c -L. -lmyhello -o hello

此时就生成了可执行程序hello。不过,当我们执行该程序的时候,会发生错误:

错误信息为“当加载共享库的时候,无法找到libmyhello.so的位置:没有该文件或目录”。

上文说过,动态库在链接阶段并没有真正地整合到程序中,而是保留了一个指向该库的“线索”。当程序在加载该动态库的时候,需要依照线索找到 动态库所在的位置。对于Linux系统而言,在可执行程序加载动态库的时候,不仅要知道该库的名字,还需要知道其绝对路径。因此,我们需要再声明 动态库的绝对位置,这样才能正确地加载动态库。

我们可以使用ldd指令查看程序加载库的情况。在执行hello程序的时候,系统无法找到libmyhello.so的绝对路径,因此无法加载库。

第四步:定位自己制作的动态库。

要想让自己制作的动态库生效,我们需要了解正常情况下系统是如何加载一个动态库的。以我们熟悉的stdio库为例,系统在加载标准输入输出库时有 以下几个步骤:(见附图2)

⒈执行./hello指令,终端解释该指令,终端指示应加载动态库stdio,寻找存放动态库的配置文件。

⒉存放动态库的配置文件默认目录为/etc/ld.so.conf.d/以及下属的众多子目录内的配置文件。配置文件指示该库的绝对路径在/usr/lib或/lib下。

⒊去往/usr/lib或/lib,将存储的stdio库加载到程序hello中。

因此我们有三种方法让自己制作的动态库生效:

⒈把自己制作的库拷贝到/usr/lib和/lib下。

⒉在LD_LIBRARY_PATH环境变量中添加自己制作的库所在的位置。

⒊添加/etc/ld.so.conf.d/XXX.conf文件(XXX需要自己命名),把库所在的路径添加到文件末尾并执行ldconfig刷新。

(注意以下三种方法的异同,以及每种方法执行时ldd指令所显示的区别。)

第一种:将库拷贝到/usr/lib和/lib下。

sudocp libmyhello.so /usr/lib

sudocp libmyhello.so /lib

此时再执行./hello即可得到正确的显示结果。

第二种:修改LD_LIBRARY_PATH环境变量

sudo vim /etc/bash.bashrc

在文件后,添加:

export

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linux/dongtaiku

保存退出,重启终端,此时再执行./hello即可得到正确的显示结果。

第三种:添加/etc/ld.so.conf.d/XXX.conf文件

sudo vim /etc/ld.so.conf.d/my.conf

在文件内添加动态库的目录:

/home/linux/dongtaiku

保存退出,执行ldconfig使设置生效。

sudoldconfig

此时再执行./hello即可得到正确的显示结果。

以上就是让动态库生效的三种方法。

四、结语

库的存在,使得我们的编程过程大大简化,程序员可以将更多的精力放在程序的逻辑上。并且,库的产生直接改变了人们对于编程语言的认识,可以 说间接促进了当代许多面向对象语言的诞生。当代的许多语言,例如c++、java、c#、javascript包括近大火的Node.js,都是集合了大量的工具库 ,库内包含了许多种编程时可能调用的功能。这样大大地方便了程序开发人员。

后留一道思考题给各位:当静态库与动态库的库名重名时,系统在加载库时是以哪个库为准呢?例如有静态库libmyhello.a和动态库libmyhello.so ,在编译时,都执行:

gcctest.c –L. –lmyhello –o hello

这时系统以哪个库为准呢?

上一篇:一瞥Unity集成开发环境中的软件工程设计思想

下一篇:属性动画

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部