【Linux静态库和动态库】

在这里插入图片描述


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 14 17:45 lib/
-rwxr-xr-x 1 gec gec   8288 14 17:46 main1 # 默认动态链接
-rwxr-xr-x 1 gec gec 845120 14 17:46 main2 # 指定静态链接
-rw-r--r-- 1 gec gec     56 14 17:42 main.c
gec@ubuntu:~$ 

很明显可以看到,静态链接的文件尺寸要大得多。


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qt历险记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值