Linux动态库和静态库

Linux动态库和静态库

一、认识动静态库

  1. 静态库:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库,在Linux中静态库是以(.a)为后缀;
  2. 动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。在Linux中静态库是以(.so)为后缀;
  3. 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码;
  4. 在可执行文件开始运行之前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking);
  5. 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间;

我们可以通过【ldd 可执行程序文件名】来查看可执行程序所依赖的库。

image-20240206204341532

​ 其中 /lib64/libc.so.6 就是可执行程序的库文件,它其实是一个软连接。我们可以通过以下命令来查看

ls /lib64/libc.so.6 -l

image-20240206204636992

如何辨别它采用的是哪一种库呢?

  1. 我们可以通过后缀来区分(在Linux中)

    在Linux中,以.so结尾的后缀,是动态库;以.a结尾的是静态库
    在Windows中,以.dll 结尾的后缀,是动态库;以.lib结尾的是静态库

    注:
    库文件的名字:libxxx.so 和 libxxx.a
    库的真实名字:去掉lib前缀,去掉.a、.so后缀,剩下的就是库的名称。

  2. 我们还可以通过file命令查看
    image-20240206205529698

​ 上图中分别使用了动静态库对同一个文件进行编译的,通过file命名可以查看到所对应的链接信息。

二、回顾编译链接的过程

在Linux中,gcc的编译可以分为以下四个步骤:

  • 预处理:在预处理阶段主要负责的是头文件的展开、去掉注释、宏替换、条件编译等。以#号开头的是预处理指令:#define #if #include ……此阶段产生【.i文件】 gcc -E mytest.c -o test.i
  • 编译:此阶段完成语法和语义分析,然后生成中间代码,此中间代码是汇编代码,但是还不可执行,gcc编译的中间==文件是【.s】==文件。在此阶段会出现各种语法错误和语义错误,特别要小心未定义的行为,这往往是致命的错误。gcc -S test.i -o test.s
  • 汇编:此阶段主要完成将汇编代码翻译成机器码指令,并将这些指令打包形成可重定位的目标文件,【.o】文件,是二进制文件。此阶段由汇编器完成。gcc -c test.s -o test.o
  • 链接:此阶段完成文件中调用的各种函数跟静态库和动态库的链接,并将它们一起打包合并形成目标文件,即可执行文件。此阶段由连接器完成。gcc test.o -o test

从以上四个阶段来看,我们要使用自己制作的库或者别人的库,一定是汇编完成后产生的.o文件,我们只需要对这个.o文件进行链接就可以了;

三、库的制作和使用

​ 库是一个二进制文件,想要使用库(给别人使用自己的制作的库或者使用别人的库)一定是由三个部分组成:库文件、头文件、文档说明;一般这个库就是函数的定义,头文件就是函数声明,我们只需要将这些打包好,别人使用我们头文件所给的接口就行。

​ 编写如下四个文件:其中源文件包含add.c和sub.c,头文件包含add.h和sub.h;用来制作静态库并打包

//add.h
#pragma once
#include <stdio.h>
extern int my_add(int x, int y); 
//add.c
#include "add.h" 
int my_add(int x, int y) {
     return x + y;                                                 
}
//sub.h
#pragma once
#include <stdio.h>
extern int my_sub(int x, int y); 
//sub.c
#include "sub.h"                                                   
int my_sub(int x, int y){
    return x - y;
}

1.静态库的制作

1.生成二进制(.o)文件

​ 首先将上面的.c文件生成.o文件

image-20240206215022818

2.打包

​ 我们将生成好的.o文件进行打包:ar -rc libmymath.a add.o sub.o

​ ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r选项和-c选项进行打包。

  1. -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  2. -c(create):建立静态库文件。

image-20240206215521726

​ 我们可以用 ar 命令的 -t 选项 和 -v 选项查看静态库当中的文件。

  • -t:列出静态库中的文件。
  • -v:显示详细的信息

image-20240206215735557

3.发布静态库

​ 静态库要发布出去供别人使用,只要库文件(所有的.o文件)是不够的,我们需要将其和头文件一起发布出去,别人只要看到头文件,就大致了解如何使用了

image-20240206220147193

​ 当然,我们也可以之间编写Makefile,就不需要我们一步一步的完成静态库的打包及发布

[ljh@VM-8-16-centos test]$ cat Makefile
libmymath.a:sub.o add.o
	ar -rc $@ $^

%.o:%.c
	gcc -c $<

.PHONY:output
output:
	mkdir output
	cp -rf *.h output
	cp libmymath.a output

image-20240206220622282

2.静态库的使用

方法一

​ 上文中,我们已经有了静态库output了,别人该如何使用呢?例如:想要在friend文件下使用这个库:
image-20240206222113673

​ 现在在friend目录下有一个mytest.c文件和一个静态库文件lib,mytest.c想要使用lib,我们先编写一下mytest.c代码:

#include "add.h"
#include "sub.h"
int main() {
    int x = 30;
    int y = 20;
    int ret1 = my_add(x, y);
    int ret2 = my_sub(x, y);
    printf("ret1 = %d\n",ret1);
    printf("ret2 = %d\n",ret2);
    return 0;
}

进行编译:

image-20240206222400928

​ 编译后,报警告:没有头文件,可是明明在lib文件下有我们要的头文件以及库文件,这是为什么呢?
​ 其实,编译器在编译的时候,会在当前的目录的文件中去找,不会去当前目录的文件夹中去找,lib目录下的头文件以及库文件和mytest.c不是同级目录,所以编译会出错;
​ 我们在编译的时候就需要告诉编译器,需要的头文件在哪个目录下。gcc mytest.c -I ./lib

image-20240206222803665

​ 此时,又有警告了:链接错误,未定义的两个函数?在lib目录下以及定义了两个函数,并且打包好了,为什么还是报错呢?
​ 其原因和上面一样;所以我们还需要告诉编译器库文件在lib目录下:gcc mytest.c -I ./lib -L ./lib

image-20240206223002695

​ 头文件和库文件所在的位置都告诉编译器了,怎么还是报错呢?
​ 其实,头文件和库文件都在lib目录下,在mytest.c文件中,是明确的包含了,add.h和sub.h的,gcc在编译的时候能够认识,但不认识库文件,如果在lib目录下有多个库文件,gcc是不知道你想要使用哪个库的。所以我们还需要指明库的名字。gcc mytest.c -I ./lib -L ./lib -l mymath

image-20240206223323385

​ 此时,整个程序才能正确编译并且运行

总结:

​ 我们在使用静态库进行编译链接时,需要指定头文件的所在路径,库文件的所在路径以及所要用掉的库名称

  • -I(大写i):指定头文件所在路径。
  • -L:指定库文件所在路径。
  • -l(小写l):指明需要链接库文件路径下的哪一个库

gcc mytest.c -I ./lib -L ./lib -l mymath

image-20240206223832921

​ 同样,我们也可以编写Makefile:

[ljh@VM-8-16-centos friend]$ cat Makefile 
mytest:mytest.c
	gcc -o $@ $^ -I ./lib -L ./lib -l mymath
.PHONY:clean
clean:
	rm -f mytest

image-20240206224108241

方法二

​ 对比我们之前在编译某个.c文件时,为什么没有加上这些选项呢?这是因为之前的库都是在系统的默认路径下,所以我们可以将我们做好的静态库拷贝到系统的默认路径下,也是可以达到不需要加这些选项的效果;但是严重不推荐!

3.动态库的制作

​ 编写如下四个文件:其中源文件包含add.c和sub.c,头文件包含add.h和sub.h;用来制作动态库并打包

//add.h
#pragma once
#include <stdio.h>
extern int my_add(int x, int y); 
//add.c
#include "add.h" 
int my_add(int x, int y){
     return x + y;                                                 
}
//sub.h
#pragma once
#include <stdio.h>
extern int my_sub(int x, int y); 
//sub.c
#include "sub.h"                                                   
int my_sub(int x, int y){
    return x - y;
}
1.生成二进制(.o)文件

​ 首先将上面的.c文件生成.o文件

 gcc -fPIC -c add.c -o add.o
 gcc -fPIC -c sub.c -o sub.o

-fPIC:作用是告知编译器 生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库。

image-20240207150040832

2.打包

​ 我们将生成好的.o文件进行打包:

gcc -shared -o libmymath.so add.o sub.o

-shared:linux在gcc编译时加上 -shared 参数时,目的是使源码编译成动态库.so 文件;

image-20240207150352140

3.发布动态库

​ 将动态库和所有的头文件组织起来,放到lib目录下,这样就可以发布动态库了

image-20240207150604612

当然,我们也可以直接编写Makefile,就不需要我们一步一步的完成静态库的打包及发布

[ljh@VM-8-16-centos 2024_2_7]$ cat Makefile 
libmymath.so:add.o sub.o
	gcc -shared -o $@ $^
%.o:%.c
	gcc -fPIC -c $<
.PHONY:clean
clean:
	rm -rf libmymath.so *.o lib
.PHONY:lib
lib:
	mkdir lib
	cp *.h lib
	cp libmymath.so lib

image-20240207151213978

4.动态库的使用

​ 动态库的使用大致和静态库类似,但略有区别。我们先使用链接静态库的方法来实现动态库的链接试试。

image-20240207152125865

能够成功编译,但是运行却报错了,为什么呢?
我们通过ldd命令列出动态库依赖关系,发现是not found。虽然已经告诉了编译器库文件和头文件的路径所在位置,但是当编译器编译好后,就与编译器无关了;当我们执行(运行)可执行程序a.out时,是由加载器来完成的。所以我们需要在运行时,告诉系统库文件在哪里;
image-20240207152415377

方法一

​ 拷贝到系统的默认路径下,一般是/usr/lib 这里不做演示,严重不推荐

方法二

​ 更改LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/home/ljh/2024_2_7/friend/lib

​ LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径。注意,LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找;

image-20240207153335991

​ 添加好后,我们再次查看,发现路径已经指定好了

image-20240207153426170

再次运行:

image-20240207153452353

四、动态库与静态库特点总结

静态库的特点:

  • 静态库在可执行程序链接时就加入到可执行代码中,在物理上称为可执行程序的一部分;程序运行时不再需要该静态库。
  • 相对于动态库链接生成的程序,静态库相当于编译器将代码补充完整了,因此执行程序会大一些,但是运行起来相对快些;
  • 静态库是牺牲了空间效率,换取了时间效率

动态库的特点:

  • 动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在;
  • 动态库只有在程序执行时,那些需要的函数才被拷贝到内存中。这样就使得可执行文件比较小,节省磁盘空间;
  • 由于运行时要连接库会花费一定的时间,执行速度相对会慢一些;
  • 动态库是牺牲了时间效率,换取了空间效率;
  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Li小李同学Li

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

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

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

打赏作者

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

抵扣说明:

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

余额充值