Linux练级宝典->动态库和静态库

动静态库的原理

我们知道可执行文件前的4步骤 预编译->编译->汇编->链接

  • 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
  • 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
  • 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
  • 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。

例如,我们上面用5个文件组合出了可执行文件。.0文件就是汇编后的文件。

我们在最后一步的时候不链接,而是把这些.o文件打包,

这样的一堆.o文件组合到一起就是一个库了。

认识动静态库

cpp 复制代码
#include <stdio.h>

int main()
{
	printf("hello world\n"); //库函数
	return 0;
}

上述代码我们用了一个库函数printf。

生成后,我们用ldd查看一个可执行程序依赖的库文件。

libc.so.6就是依赖的文件。

我们找到上面的文件目录,我们看到我们上面的连接其实就是一个软连接。

我们可以查查看这个库文件

我们可以看到他是一个shared object。

shared object说明这就是一个共享的目标文件库,准确来说,还是一个动态库

  • linux中 以.so为后缀的是动态库,以.a为后缀的是静态库
  • Windows中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。

这里可执行程序锁依赖的.so文件就是动态库,gcc/g++默认的是动态链接,若想进行静态链接,可以加一个-static选项。

cpp 复制代码
gcc -o mytest-s mytest.c -static

可以看到我们静态库 的大小远远大于可执行文件。

我们查看二者的情况,一个是dynamically 动态链接 一个是 statically 静态链接。

动静态库各自的特征

静态库

静态库是在编译链接时期,直接把库的代码复制一份到可执行文件中,生成的可执行程序在运行的时候不再需要静态库,所以里面多了一个库的代码所以肯定会大。

优点:

静态库,只要生成可执行程序后,这个程序就可以独立运行了,静态库就没用了

缺点:

由上面也可以看出静态库生成的代码会占用大量的空间,并且如果这个库被多个程序使用,就会导致每个程序都多一个静态库代码。代码还都是重复的

动态库

动态库:就是在程序运行时,在共享区中找到对应的动态库,此时去动态库中调用代码。

动态库就相当于,一开始就在物理内存加载好一份,然后程序调用对应代码时,通过虚拟内存和物理内存映射的方式,找到对应的函数。

优点:

节省空间,多个库调用的都是在物理内存中的同一个库,即同一个代码。

缺点:

必须依赖动态库,否则无法运行。

静态库的打包与使用

我们写4个文件,add.h和add.c和sub.h和sub.c都是很简单的代码

就是一个头文件,一个原文件,里面就一个加减函数。头文件声明这个函数。

1.生成.o文件。

生成两个.o文件 gcc -c选项。

2.使用ar命令将所有目标文件打包为静态库

ar指令常用于将目标文件打包为静态库

-r: 若静态库文件中的目标文件有更新,则用新的目标文件替换为旧的目标文件(replace)

-c:建立静态库文件(create)

cpp 复制代码
ar -rc libcal.a add.o sub.o

-t:列出静态库的文件

-v:显示详细信息。

3.把头文件和生成的静态库组织起来

当我们把自己的库给别人用的时候,可以看到上面我们有一个静态库文件和剩下的.h的文件,所以我们就是要把头文件和.o的静态库文件给到别人就可以。

makefile文件:

bash 复制代码
mylib=libcal.a
CC=gcc

$(mylib) : add.o sub.o
	ar -rc -o $(mylib) $^
%.o:%.c
	$(CC) -c $<

.PHONY:clean
clean:
	rm -f $(mylib) ./*.o

.PHONY:output
output:
	mkdir -p mathlib/include
	mkdir -p mathlib/lib
	cp ./*.h mathlib/include
	cp ./*.a mathlib/lib

make编译文件,makeout将文件放入mathlib文件夹中。

此时就是把mathlib给到别人,此时别人有静态库和头文件.h,只差最后一步链接即可使用。

使用静态库

创建一个源文件main.c,然后使用库函数。

cpp 复制代码
#include <stdio.h>
#include <add.h>

int main()
{
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

此时我们的文件夹里只有两个文件,main.c和我们打包的静态库

方法一:使用选项

此时使用gcc编译main.c文件生成可执行程序就需要携带三个选项了

  • -I : 指定头文件的搜索路径
  • -L:指定库文件的搜索路径
  • -l:指明需要链接库文件路径下的哪一个库

指定库代码如下:

cpp 复制代码
gcc main.c -I ./mathlib/include/ -L ./mathlib/lib/ -lcal

结果如下:

为什么要指定搜索文件?

我们知道操作系统中有环境变量这个说法,所以我们在使用一些系统的库的时候,没有指定路径,而是直接使用,而我们自己定义的库没在环境变量下,此时使用就得带上指定的路径,让系统去找。当然也可以把这个路径放到环境变量下。

1.指定头文件的路径,是找到头文件->找到声明。

2.指定库的路径,是找到库->找到定义。

3.因为我们库可能不只是一个,所以我们要指定需要找到哪个库文件,所以-lcal就是指定名字。

4.上述三个选项后可以加空格也可以不加。

方法二:把头文件和库文件塞到对应的系统路径下

就是进入环境变量的意思,放到系统路径后就能让操作系统自动找到对应库了。

将库文件拷贝进系统路径下。

bash 复制代码
sudo cp mathlib/lib/libcal.a /lib64/
sudo cp mathlib/include/* /usr/include/

结果如下:

我们发现我们把文件放到系统路径后,只用指定库的名字就完成了调用。

实际我们把头文件和库文件塞到系统路径就是安装库的过程,但不推荐将自己写的头文件和库文件拷贝到系统路径下,这样其实是造成污染的。

动态库的打包与使用

打包

动态库和静态库都是库,但是二者在打包时有一点点区别。我们还是使用上述的4个文件进行打包

1.还是让源文件生成.o文件

这里生成文件时,需要多加一步操作:

带上选项 -fPIC(position independent code):产生位置无关码。

cpp 复制代码
gcc -fPIC -c add.c sub.c

选项说明:

-c 生成.o文件。

-fPIC :生成位置无关码。告诉编译器产生位置无关码,给动态库使用

2.使用-shared选项将所有目标文件打包为动态库

和静态库ar 指令不同,动态库的生成用 gcc 带 -shared选项。

bash 复制代码
gcc -shared -o libcal.so add.o sub.o

我们发现此时生成的动态库的大小 << 静态库的大小。

3.将头文件和生成的静态库组织到一个文件夹下面

使用makefile把文件组织起来

原始步骤就是

我们用makefile将指令操作汇总一下:

cpp 复制代码
mylib=libcal.so

%.o:%.c
	gcc -fPIC -c $<
$(mylib):add.o sub.o
	gcc -shared -o $(mylib) $^


.PHONY:clean
clean:
	rm -rf $(mylib) ./*.o

.PHONY:output
output:
	mkdir -p mlib/include
	mkdir -p mlib/lib
	cp ./*.h mlib/include
	cp ./*.so mlib/lib

注意:makefile的缩进要用tab键,用空格键可能会出错。

结果如下:

此时继续使用main.c测试整个库代码。

cpp 复制代码
#include <stdio.h>
#include <add.h>

int main()
{
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}
cpp 复制代码
 gcc main.c -I./mlib/include -L./mlib/lib -lcal

使用指令后,生成可执行文件。

我们发现文件不能执行。

我们发现我们的动态库文件是not found的。

方法一:拷贝.so文件到系统共享路径下

这里就是让你把.so拷贝到和 c库文件同一个目录下,此时操作系统就会自动找到库了。

cpp 复制代码
sudo cp ./libcal.so /lib/x86_64-linux-gnu/

方法二:更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是一个环境变量,这个环境变量是程序运行时要搜索的路径。所以把动态库所在的目录添加到LD_LIBRARY_PATH即可。

cpp 复制代码
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/~/blog-code/DSL-D

我们看到.so上面也有一串地址,说明当前库是找到了。

方法三:配置/etc/ld.so.conf.d/

这个目录下面放的都是以.conf为后缀的配置文件,这些配置文件就是一堆路径,和环境变量一样,系统会自动进行查找。

cpp 复制代码
echo ~/blog-code/DSL-D/mlib/lib > aron.conf
sudo cp aron.conf /etc/ld.so.conf.d/

上述两个命令:第一个是将上面的地址打印到aron.conf中

第二个是把aron.conf放到上述目录中。

cpp 复制代码
sudo ldconfig

使用上述指令刷新配置文件。

此时执行文件

相关推荐
对你无可奈何29 分钟前
关于Ubuntu的 update造成的内核升级
运维·服务器·ubuntu
qq_312920111 小时前
Nginx限流与防爬虫与安全配置方案
运维·爬虫·nginx·安全
GanGuaGua1 小时前
Linux系统:线程的互斥和安全
linux·运维·服务器·c语言·c++·安全
lsnm1 小时前
【LINUX网络】IP——网络层
linux·服务器·网络·c++·网络协议·tcp/ip
全糖去冰吃不了苦1 小时前
ELK 集群部署实战
运维·jenkins
不掰手腕1 小时前
在UnionTech OS Server 20 (统信UOS服务器版) 上离线安装PostgreSQL (pgsql) 数据库
linux·数据库·postgresql
Lynnxiaowen2 小时前
今天继续昨天的正则表达式进行学习
linux·运维·学习·正则表达式·云计算·bash
努力学习的小廉2 小时前
深入了解linux系统—— POSIX信号量
linux·运维·服务器
刘一说2 小时前
CentOS部署ELK Stack完整指南
linux·elk·centos
从零开始的ops生活2 小时前
【Day 50 】Linux-nginx反向代理与负载均衡
linux·nginx