Linux静态库和动态库
- 1. 编译与ELF格式
- 2. 库的基本概念
- 3.静态库的制作:(假设要将a.c、b.c制作成静态库)
- 4.静态库的常见操作
- 5.静态库的使用
- 6. 多个库的相互依赖
- 举例1.(库文件制作、错误处理)
- 7.静态库和动态库的关系和区别
- 8.动态库的制作
- 软链接 | 硬链接
- 软连接和硬链接区别
1. 编译与ELF格式
预处理:解释并展开源程序当中的所有的预处理指令,此时生成 *.i 文件。(宏替换)
命令:gec@ubuntu:~$ gcc hello.c -o hello.i -E
编译:词法和语法的分析,生成对应硬件平台的汇编语言文件,此时生成 *.s 文件。
命令:gec@ubuntu:~$ gcc hello.i -o hello.s -S
汇编:将汇编语言文件翻译为对应处理器的二进制机器码,此时生成 *.o 文件。
命令:gec@ubuntu:~$ gcc hello.s -o hello.o -c
链接:将多个 *.o 文件合并成一个不带后缀的可执行文件。
命令:gec@ubuntu:~$ gcc hello.o -o hello -lc
重点关注最后一步,库文件的链接:链接实际上是将多个.o文件合并在一起的过程。这些 *.o 文件合并前是 ELF 格式,合并后也是 ELF
格式。ELF全称是 Executable and Linkable Format,即可执行可链接格式
库的本意是library图书馆,库文件就是一个由很多 *.o 文件堆积起来的集合。 ELF需要对各个 *.o
文件中的静态数据(包括常量)、函数入口的地址做统一分配和管理,这个过程就叫做“重定位”,因此未经链接的单独的 *.o
文件又被称为可重定位文件,经过链接处理合并了相同的段的文件称为可执行文件。
2. 库的基本概念
库文件分为两类:静态库和动态库。如:
静态库:libx.a
动态库:liby.so
lib库名.后缀
其中,lib是任何库文件都必须有的前缀,库名就是库文件真正的名称,比如上述例子中两个库文件分别叫x和y,在链接它们的时候写成 -lx 和 -ly ,后缀根据静态库和动态库,可以是 .a 或者 .so:
静态库的后缀:.a (archive,意即档案)
动态库的后缀:.so (share object,意即共享对象)
注意:不管是静态库,还是动态库,都是可重定位文件 *.o 的集合。
3.静态库的制作:(假设要将a.c、b.c制作成静态库)
*第一步,制作 .o 原材料
gec@ubuntu:~$ gcc a.c -o a.o -c
gec@ubuntu:~$ gcc b.c -o b.o -c
*第二步,将 .o 合并成一个静态库
gec@ubuntu:~$ ar crs libx.a a.o b.o
4.静态库的常见操作
#查看 *.o 文件
gec@ubuntu:~$ ar t libx.a #(t意即table,以列表方式列出*.o文件)
a.o
b.o
# 删除 *.o 文件
gec@ubuntu:~$ ar d libx.a b.o #(d意即delte,删除掉指定的*.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
# 增加 *.o 文件
gec@ubuntu:~$ ar r libx.a b.o #(r意即replace,添加或替换(重名时)指定的*.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
b.o
# 提取 *.o 文件
gec@ubuntu:~$ ar x libx.a #(x意即extract,将库中所有的*.o文件释放出来)
gec@ubuntu:~$ ar x libx.a a.o #(指定释放库中的a.o文件)
5.静态库的使用
库文件最大的价值,在于代码复用。假设在上述库文件所包含的*.o文件中,已经包含了若干函数接口,那么只要能链接这个库,就无需再重复编写这些接口,直接链接即可。
假设a.c中包含了如下函数:
// a.c
void func()
{
printf("我是a.c中的函数func\n");
}
那么,就可以使用链接库的形式,使用这个接口:
// main.c
void func(); //函数要声明
int main()
{
func();
return 0;
}
编译并运行的结果是: -L/库路径 -l库名 -i 头文件路径
gec@ubuntu:~$ gcc main.c -L/home/gec -lx -o main
gec@ubuntu:~$ ./main
我是a.c中的函数func
gec@ubuntu:~$
注意:
编译语句中的 -L/home/gec 指明库文件 libx.a 的具体位置,否则系统找不到该库文件。 编译语句中的 -lx
指明要链接的库文件的具体名称,注意不包含前缀后缀。 对于静态库而言,由于编译链接时会将 main.c
所需要的库代码复制一份到最终的执行文件中,这直接导出静态库的如下特性: 执行程序在编译之后与静态库脱离关系,其执行也不依赖于静态库。
执行程序执行时由于不依赖静态库,因此也省去了运行时动态。
6. 多个库的相互依赖
假设有两个库文件:liba.a 和 libb.a,它们分别只包含了 a.o 和 b.o,假设这两个源程序有如下依赖关系:
// a.c
#incldue <stdio.h>
void fa()
{
printf("Hey!\n");
}
// b.c
#incldue <stdio.h>
void fa(); //fa()在这里被声明了
void fb()
{
fa(); // fb() 调用了 fa(),即libb.a依赖于liba.a
}
很明显,b.c中的功能接口是依赖于 a.c 的,换句话说,库文件 libb.a 是依赖于 liba.a 的。
现在再来写一个调用 fb() 的主函数:
void fb();
int main(void)
{
fb();
}
编译情况如下:
gec@ubuntu:~$ gcc main.c -o main -L. -lb -la //谁要用我的方法,谁就在我的前面
gec@ubuntu:~$ gcc main.c -o main -L. -la -lb
./libb.a(b.o): In function `fb':
b.c:(.text+0xa): undefined reference to `fa'
collect2: error: ld returned 1 exit status
从以上编译信息来看,得出结论:
当编译链接多个库,且这些库之间有依赖关系时,被依赖的基础库要放在编译语句的后面。 在以上示例中,库 libb.a 依赖于 liba.a,即
liba.a 是被依赖的基础库,因此 -la 要放在 -lb 的后面才能通过编译。 注意:以上结论对于静态库、动态库都适用。
举例1.(库文件制作、错误处理)
【1】将常用的文件IO函数封装成自报错的静态库和动态库,并在要以后的程序中根据需要调这些库文件。read.c
例如:
gcc read.c -o read.o -c 生成连接文件
ar crs libread.a read.o 生成静态库
ssize_t Read(int fd, void *buf, size_t count)
{
int total = 0;
int n;
while(count > 0)
{
while((n=read(fd, (char *)buf+total, count))==-1 &&
errno == EINTR);
//(char *)buf+total : buf指针,指向每次读取到的数据 加total确保数据不被覆盖 准确定位数据增加后每次的位置
if(n == -1) // 遇到了错误
{
perror("read失败");
return -1;
}
if(n == 0) // 遇到了文件尾时=0
break;
count -= n; //n每次读到的大小 count 总的大小 count = count-n :减去每次读的数据
total += n; //total统计每次读到的数据 total =total+n
}
// 返回总共读到的字节数
return total;
}
7.静态库和动态库的关系和区别
静态库(相当于书店,只卖不借)
原理:编译时,库中的代码将会被复制到每一个程序中
优点:程序不依赖于库、执行效率稍高
缺点:浪费存储空间、无法对用户升级迭代
动态库(相当于图书馆,只借不卖)
原理:编译时,程序仅确认库中功能模块的匹配关系,并未复制
缺点:程序依赖于库、执行效率稍低
优点:节省存储空间、方便对用户升级迭代
8.动态库的制作
lib库名.后缀 对于动态库而言,在后缀后面还经常会带着版本号: lib库名.后缀.版本号 完整的动态库文件名称是:
lib库名.so.主版本号.次版本号.修订版本号,比如: libx.so.1.3.1 动态库一般会用一个只带主版本号的符号链接(软连接ln
-s 生成)来链接程序(方便版本更新换代),
软链接 | 硬链接
1、软链接就是:“ln –s 源文件 目标文件”,只会在选定的位置上生成一个文件的镜像,不会占用磁盘空间,类似与windows的快捷方式。
2、硬链接ln源文件 目标文件,没有参数-s, 会在选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
2.通过实验加深理解
[oracle@Linux]$ vi test.log
#创建一个测试文件f1
[oracle@Linux]$ ln test.log test1.log
#创建f1的一个硬连接文件test1.log
[oracle@Linux]$ ln -s test.log test2.log
#创建f1的一个符号连接文件test2.log
[oracle@Linux]$ ls -li
# -i参数显示文件的inode节点信息
软连接和硬链接区别
1.软连接有个主体,删除其他一个不会影响,其余的连接,但是删除主体(源文件),所有文件都失效变红
2.硬链接主次不分,只要生成以后,删除谁都不影响。
如:
gec@ubuntu:~$ ls -l
lrwxrwxrwx 1 root root 15 Jan 16 2020 libbsd.so.0 -> libbsd.so.0.8.7
动态库的制作
不管是静态库还是动态库,都是用来被其他程序链接的一个个功能模块。与静态库一致,制作动态库的步骤如下:
将 *.c 编译生成 *.o
将 *.o 编译成动态库
例如:
gec@ubuntu:~$ ls
a.c b.c
# 第一步:将源码编译为 *.o
gec@ubuntu:~$ gcc a.c -o a.o -c -fPIC
gec@ubuntu:~$ gcc b.c -o b.o -c -fPIC
gec@ubuntu:~$ ls
a.c b.c a.o b.o
# 第二步:将 *.o 编译为动态库
gec@ubuntu:~$ gcc -shared -fPIC -o libx.so a.o b.o
gec@ubuntu:~$ ls
a.c b.c a.o b.o libx.so
# 第三步:将连接库生成可执行文件
gec@ubuntu:~$ gcc main.c -o main -L./lib -lx
解释:当前目录lib文件夹下 -l库名 库被放在了/lib下
说明:
-L 选项后面跟着动态库所在的路径。
-l 选项后面跟着动态库的名称
如果程序运行时找不到动态库,运行就会失败,例如:
gec@ubuntu:~$ ./main
报错
出现上述错误的原因,就是因为运行程序 main 时,无法找到其所依赖的动态库 libx.so,解决这个问题,有三种办法:
编译时预告:
gec@ubuntu:~$ gcc main.c -o main -L. -lx -Wl,-rpath=/home/gec/lib
设置环境变量:
gec@ubuntu:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec/lib
修改系统默认库路径:(不推荐)
gec@ubuntu:~$ sudo vi /etc/ld.so.conf.d/libc.conf
gec@ubuntu:~$ sudo ldconfig
在以上文件中,添加动态库所在路径即可。 注意: 此处要小心编辑,一旦写错可能会导致系统无法启动,这是一种 污染 系统的做法,不推荐。
在gcc中,如果遇到动态库和静态库同名,则会默认优先进行动态链接,如果此时需要用静态链接,可以使用编译选项 -static 达到此目的
gec@ubuntu:~$ ls ./lib # 假设有两个重名的静态、动态库
libx.a libx.so
gec@ubuntu:~$
gec@ubuntu:~$ gcc main.c -o main1 -L./lib -lx
gec@ubuntu:~$ gcc main.c -o main2 -L./lib -lx -static
gec@ubuntu:~$ ls -l
total 868
drwxr-xr-x 2 gec gec 4096 1月 4 17:45 lib/
-rwxr-xr-x 1 gec gec 8288 1月 4 17:46 main1 # 默认动态链接
-rwxr-xr-x 1 gec gec 845120 1月 4 17:46 main2 # 指定静态链接
-rw-r--r-- 1 gec gec 56 1月 4 17:42 main.c
gec@ubuntu:~$
很明显可以看到,静态链接的文件尺寸要大得多。