您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > linux

Linux下动态库(.so)和静态库(.a) 的区别

时间:2020-10-12 09:25:12  来源:  作者:

linux下有两种库:动态库和静态库(共享库)

二者的不同点在于代码被载入的时刻不同。

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

动态库(共享库)的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,因此代码体积比较小。

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

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

静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库

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

 

一 静态库

这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

静态库的代码在编译时链接到应用程序中,因此编译时库文件必须存在,并且需要通过“-L”参数传递路径给编译器,应用程序在开始执行时,库函数代码将随程序一起调入进程内存段直到进程结束,其执行过程不需要原静态库存在。

在UNIX中,使用ar命令创建或者操作静态库

ar archivefile objfile

archivefile:archivefile是静态库的名称

objfile:objfile是已.o为扩展名的中间目标文件名,可以多个并列

参数 意义

-r 将objfile文件插入静态库尾或者替换静态库中同名文件

-x 从静态库文件中抽取文件objfile

-t 打印静态库的成员文件列表

-d 从静态库中删除文件objfile

-s 重置静态库文件索引

-v 创建文件冗余信息

-c 创建静态库文件

example:

/****************** hello.h **************/  
  void hello(void);  
/****************** hello.cpp **************/  
  #include<IOStream>  
#include"hello.h"  using namespace std;  void hello(void)  {            cout <<"Hello "<<endl;  
}  
/****************** main.cpp **************/  
  
#include"hello.h"  
  
int main(int argc,char *argv[])  
{          hello();          return 0;  }  

1.编译成静态库

无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。

hc@linux-v07j:~/weiming/tt> g++ -o hello.o -c hello.cpp

hc@linux-v07j:~/weiming/tt> ar cqs libHello.a hello.o

hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp

2.链接

hc@linux-v07j:~/weiming/tt> g++ main.cpp libHello.a -o Out1 (g++ -o aOut main.cpp ./libHello.a 或者 g++ -o aOut main.cpp -L./ -lHello)

注意:如果hello() 里面还使用了其他的库函数比如pthread_create,则最后生成Out1 时还需 -lpthread,但ar 时可以不用,只需要在 include 的头文件中找到函数符号声明即可,但最终生成可执行文件时需要找到所有的符号定义。

hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp Out1

 

hc@linux-v07j:~/weiming/tt> ldd Out1
linux-gate.so.1 => (0xffffe000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e36000)
libm.so.6 => /lib/libm.so.6 (0xb7e11000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7e06000)
libc.so.6 => /lib/libc.so.6 (0xb7ce3000)
/lib/ld-linux.so.2 (0xb7f1b000)

 

二: 动态库

这类库的名字一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便

不同的UNIX系统,链接动态库方法,实现细节不一样

 

编译PIC型.o中间文件的方法一般是采用C语言编译器的-KPIC或者-fpic选项,有的UNIX版本C语言编译器默认带上了PIC标准.创建最终动态库的方法一般采用C语言编译器的-G或者-shared选项,或者直接使用工具ld创建。

最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。(转者注:共享库各段的加载地址并没有定死,可以加载到任意位置,因为指令中没有使用绝对地址(相对于链接后的可执行文件各segment来说),因此称为位置无关代码)
-L.:表示要连接的库在当前目录中
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

这里分别将源文件d1.c和d2.c编译为动态库d1.so和d2.so.

/************ d1.h***************/  
void print();  
/***************  d1.cpp *******************/  
  #include <iostream>  
#include "d1.h"  using namespace std  int p = 1;  void print()    {        cout<< p <<endl;  
  
}  
/************ d2.h***************/  
void print();  
/***************  d2.cpp *******************/  
#include <iostream>  
#include "d2.h"  using namespace std;    int p = 2;  void print()  {      cout<< p <<endl;  
}  

LINUX和其他gcc编译器

g++ -fpic -c d1.cpp d2.cpp /* 编译为.o为扩展名的中间目标文件d1.o,d2.o*/

g++ -shared -o libd1.so d1.o /*根据中间目标文件d1.o创建动态库文件d1.so*/

g++ -shared -o libd2.so d2.o /*根据中间目标文件d2.o创建动态库文件d2.so*/

或者直接一步到位

g++ -O -fpic -shared -o libd1.so d1.cpp

g++ -O -fpic -shared -o libd2.so d2.cpp

某些版本的gcc上也可以使用-G替换-shared选项

 

调用动态库

隐式调用动态库

/**************  main.cpp *********************/  
  
void print(); //或者用#include"d1.h"(#include"d2.h")替换  
int main(int argc,char *argv[])  
{      print();  }  

#cp ./libd1.so libd.so (cp ./libd2.so libd.so )

# g++ -o dOut main.cpp ./libd.so (或者g++ -o dOut main.cpp -L./ -ld)

hc@linux-v07j:~/weiming/tt/dd> ldd dOut
linux-gate.so.1 => (0xffffe000)
libd.so => ./libd.so (0xb7f0f000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e2b000)
libm.so.6 => /lib/libm.so.6 (0xb7e06000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7dfa000)
libc.so.6 => /lib/libc.so.6 (0xb7cd8000)
/lib/ld-linux.so.2 (0xb7f12000)

 

ldd模拟运行一遍main,在运行过程中做动态链接,从而得知这个可执行文件依赖于哪些共享库,每个共享库都在什么路径下,加载到进程地址空间的什么地址。

 

总之,共享库的搜索路径由动态链接器决定,从ld.so(8)的Man Page可以查到共享库路径的搜索顺序:

1. 首先在环境变量LD_LIBRARY_PATH所记录的路径中查找。

2. 在程序链接时指定的 rpath 中查找,可以 readelf binfile | grep RPATH 。

3. 然后从缓存文件/etc/ld.so.cache中查找。这个缓存文件由/sbin/ldconfig命令读取配置文件/etc/ld.so.conf 之后生成。

(也可以在 ld.so.conf.d 目录下增加 *.conf 文件,里面写入库路径,在 ld.so.conf 中 include ld.so.conf.d/*.conf )

Linux下动态库(.so)和静态库(.a) 的区别

 

4. 如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib。

不同的UNIX所依赖的动态库查找路径环境变量名称各不相同

UNIX版本 动态库查找路径环境变量

AIX LIB_PATH

LINUX LD_LIBRARY_PATH

HP_UNIX PAHT

SCO UNIX LD_LIBRARY_PATH

 

动态链接库取代静态库的好处之一就是可以随时升级库的内容。

当动态库被接口完全相同的库文件取代后,可执行程序能迅速的切换到新动态库中代码,省去了编译的麻烦。

 

显式调用动态库

显式调用动态库,编译时无需库文件,执行时动态可存储于任意位置,库里共享对象必须先申请后使用,不同动态库版本,只要其共享对象接口相同,就可以直接动态加载。注意添加 "-ldl" 编译参数。

//打开动态库  
#include<dlfcn.h>  
void *dlopen(const char * pathname,int mode);    //获取动态库对象地址  include<dlfcn.h>  
void *dlsym(void *handle,const char *name);    //错误检测  include<dlfcn.h>  
char *dlerror(vid);    //关闭动态库  include<dlfcn.h>  
int dlclose(void * handle);  

动态库的加载或多或少会占用一定的系统资源,比如内存等。因此当不需要或者一段时间内不需要共享动态库时就要卸载之。函数dlclose关闭参数handle所指向的动态库,卸载其所占的内存等资源,此调用后参数handle无效。

实际上,由于动态库可能同时被多个进程共享,当一个进程指向dlclose时,资源并不马上被卸载,只有当全部进程都宣布关闭动态库后,操作系统才开始回收动态库资源。

 

总结:

编译静态库时先使用-c选项,再利用ar工具产生.编译动态库的方式依不同版本的UNXI而定。隐式调用动态库与静态库的用法相一致,而显示调用动态库则需要借助动态加载共享库函数族。

隐式调用动态库和静态库使用方法一致,使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?

通过测试可以发现,当静态库和动态库同名时, gcc命令将优先使用动态库.为了确保使用的是静态库, 编译时可以加上 -static 选项,因此多第三方程序为了确保在没有相应动态库时运行正常,喜欢在编译最后应用程序时加入-static



Tags:Linux 动态库   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
linux下有两种库:动态库和静态库(共享库)二者的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积比较大。动态库(共享库)的代码在...【详细内容】
2020-10-12  Tags: Linux 动态库  点击:(153)  评论:(0)  加入收藏
▌简易百科推荐
作用显示文件或目录所占用的磁盘空间使用命令格式du [option] 文件/目录命令功能显示文件或目录所占用的磁盘空间一些写法的区别du -sh xxx 显示总目录的大小,但是不会列出...【详细内容】
2021-12-23  mitsuhide1992    Tags:du命令   点击:(12)  评论:(0)  加入收藏
什么是linux内核linux就像是一个哲学的最佳实践。如果非要对它评价,我真的不知道该怎么赞叹,我只能自豪地说着:“linux的美丽简直让人沉醉。”我只能说是我处在linux学习的修炼...【详细内容】
2021-12-23  linux上的码农    Tags:linux内核   点击:(15)  评论:(0)  加入收藏
本文将比较 Linux 中 service 和 systemctl 命令,先分别简单介绍这两个命令的基础用法,然后进行比较。从 CentOS 7.x 开始,CentOS 开始使用 systemd 服务来代替 service服务(dae...【详细内容】
2021-12-23  软件架构    Tags:systemctl   点击:(13)  评论:(0)  加入收藏
mv是move的缩写,可以用来移动文件或者重命名文件名,经常用来备份文件或者目录。命令格式mv [选项] 源文件或者目录 目标文件或者目录命令功能mv命令中第二个参数类型的不同(...【详细内容】
2021-12-17  入门小站    Tags:mv命令   点击:(23)  评论:(0)  加入收藏
大数据技术AI Flink/Spark/Hadoop/数仓,数据分析、面试,源码解读等干货学习资料 98篇原创内容 -->公众号 Linux sed 命令是利用脚本来处理文本文件。sed 可依照脚本的指令来处...【详细内容】
2021-12-17  仙风道骨的宝石骑士    Tags:sed命令   点击:(21)  评论:(0)  加入收藏
Node是个啥?  写个东西还是尽量面面俱到吧,所以有关基本概念的东西我也从网上选择性地拿了下来,有些地方针对自己的理解有所改动,对这些概念性的东西有过了解的可选择跳过这段...【详细内容】
2021-12-15  linux上的码农    Tags:node   点击:(21)  评论:(0)  加入收藏
难道只有我一个人觉得Ubuntu的unity桌面非常好用吗?最近把台式机上面的Ubuntu 16.04格式化了,装了黑苹果用了一周,不得不说,MacOS确实很精美,软件生态比Linux丰富很多,比Windows简...【详细内容】
2021-12-14  地球末日村    Tags:ubuntu   点击:(34)  评论:(0)  加入收藏
简介Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。输出信息含义执行net...【详细内容】
2021-12-13  窥镜天    Tags:Linux netstat   点击:(26)  评论:(0)  加入收藏
对于较多数量的文件描述符的监听无论是select还是poll系统调用都显得捉襟见肘,poll每次都需要将所有的文件描述符复制到内核,内核本身不会对这些文件描述符加以保存,这样的设计...【详细内容】
2021-12-13  深度Linux    Tags:Linux   点击:(16)  评论:(0)  加入收藏
今天,我们来了解下 Linux 系统的革命性通用执行引擎-eBPF,之所以聊着玩意,因为它确实牛逼,作为一项底层技术,在现在的云原生生态领域中起着举足轻重的作用。截至目前,业界使用范...【详细内容】
2021-12-10  架构驿站    Tags:eBPF   点击:(24)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条