文章目录
- 前言:
- 1. 什么是动静态库
- 2. 动静态库的制作和使用
- 3. 动态库的查找问题
- 4. 理解动态库的加载
- 4.1. 站在系统的角度理解
- 4.2. 编址、可执行程序
- 4.3. 动态库动态链接和加载问题
- 总结:
前言:
在软件开发中,动静态库是两种重要的代码复用和模块化手段。静态库(.a)在程序编译时将库代码整合到可执行文件中,而动态库(.so)则在程序运行时才链接库代码,使得多个程序可以共享同一份库代码。这种机制不仅提高了开发效率,还节省了磁盘和内存空间。本文将深入探讨动静态库的概念、制作和使用,以及动态库的查找和加载机制,帮助读者更好地理解和应用动静态库。
1. 什么是动静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
a. 用过库吗? 用过,语言给我们提供了大量的库来进行链接。
b. 动态库 & 静态库: 系统默认安装动态库,云服务器,静态库(C标准库)默认是没有安装的。
c. 默认编译程序,用的是动态链接的,如果要静态加上 -static
d. libXXX.so、libYYY.a , 库真实的名字:XXX、YYY
2. 动静态库的制作和使用
站在库制作者的角度:我只给你提供 .o , 在不提供源文件的情况下也能形成可执行程序。
为什么要有库:
- 提高开发效率
- 隐藏源代码
所有的 .o 文件用特定的方式,进行打包,形成一个文件
// main.c
#include "mymath.h"
#include "mystdio.h"
#include <stdio.h>
int main()
{
int res = myAdd(10, 20);
printf("%d + %d = %d\n", 10, 20, res);
myFILE* fp = my_fopen("log.txt", "w");
if (fp == NULL) return 1;
return 0;
}
打包:
ar -rc libmyc.a mymath.o mystdio.o
gcc main.c -lmyc -L .
-L .
:如果不像让 gcc 在当前目录下找了,可以将头文件添加到系统默认的头文件和库目录下。
gcc -c src.c -> src.o
gcc -c -fPIC src.c
动态库打包,我们不需要其它工具
makefile
文件:
libmyc.so: mymath.o mystdio.o
gcc -shared -o $@ $^
%.o:%.c
gcc -c -fPIC $<
.PHONY:clean
clean:
rm -rf *.o libmyc.so
编写库的人:未来要给别人(用库的人), 交付的是: 头文件+库文件
打包为压缩包:
libmyc.a:mymath.o mystdio.o
ar -rc $@ $^
rm *.o
%.o:%.c
gcc -c $<
libmyc.so:mymath.o mystdio.o
gcc -shared -o $@ $^
%.o:%.c
gcc -c -fPIC $<
#mymath.o:mymath.c
# gcc -c -fPIC $<
#mystdio.o:mystdio.c
# gcc -c -fPIC $<
.PHONY:clean
clean:
rm -rf *.o libmyc.so *.a mylib mylib.tgz
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp -rf *.h mylib/include
cp -rf *.so mylib/lib
cp -rf *.a mylib/lib
tar czf mylib.tgz mylib
以目录的形式组织起来:(告诉编译器头文件和库所在的文件位置)
3. 动态库的查找问题
- 库没有在当前路径下
- 库没有安装到系统中
- 就是在我设定的目录内部
我们告诉的仅仅是编译器文件所在路径,可执行程序并不知道
所谓的把库(其它软件)安装到系统中,本质就是把对应的文件,拷贝到指定的路径中。
对动态库:
编译时的搜索路径 —— gcc
运行时的库搜索路径 —— OS
将库安装在系统中(/lib64),既可以支持编译,又可以支持运行。
解决找不到动态库的4种方案:
- 直接将库进行安装(拷贝)到系统中(朴实无华)(官方的库推荐)
- 将不在系统路径下的库路径,添加到 LD_LIBRARY_PATH 中
- 通过软链接的方式 (自己写的库推荐)
sudo ln -s /home/qhd/...(绝对路径) /lib64/libmyc.so
- 系统的配置文件路径方案
编译器选择库的情况:
- 如果我们同时提供动态库和静态库,gcc默认使用的时动态库。
- 如果我们非要静态链接,我们必须使用 static 选项。
- 如果我们只提供的静态库,那我们的可执行程序也是没有办法,只能对该库进行静态链接,但程序一定整体是静态链接的。
- 如果我们只提供静态库,默认只能动态链接,非得静态链接,会发生链接报错。
4. 理解动态库的加载
4.1. 站在系统的角度理解
库默认就是一个磁盘 级别的文件
库函数调用,依旧在进程的地址空间种进行的。
动态库加载之后,会被映射到进程的共享区中。
本质:所有系统进程中公共的代码和数据,只需要存在一份!
问题:
- 谁来决定,哪些库加载了,哪些没有加载? OS会自动完成
- 系统中可以同时存在非常多的已经加载到内存的库呢? 是! 操作系统要不要管理呢? 要!(先描述(用结构体描述),再组织(用链表管理起来))
struct loadlib
{
char *libname;
void *addr;
uint64_t time;
struct loadlib *next;
//...
}
4.2. 编址、可执行程序
可执行程序本身是有自己的格式信息的。
如果我们的可执行程序,没有加载到内存中,我们的程序中有没有地址呢?本来就有地址。
其实我们可执行程序,在没有加载之前,也已经基本被按照类别(比如权限,访问属性等)已经可执行程序划分为各个区域了。
我们进程地址空间里面的很多地址数据,是从可执行程序中来的 。
绝对编址的方式 —— 平坦模式
相对地址(逻辑地址) —— 段地址 + 偏移量
操作系统和编译器也有关系? 虚拟地址空间本身不仅是 OS 要遵守,编译器编译的时候也要遵守。
4.3. 动态库动态链接和加载问题
- 一般程序的加载
地址空间既然是一个数据结构对象,谁来用什么数据初始化呢?
代码是 “数据” 吗? 是
所谓的地址空间,本质是由操作系统 + 编译器 + 计算机体系结构(CPU) 三者共同配合完成的。 - 动态库
库的数据和方法访问,都是可以通过库在地址空间(起始地址 + 我们程序内部的偏移量)
总结:
本文详细介绍了动静态库的概念、制作、使用以及动态库的查找和加载问题。通过对比静态库和动态库的特点,我们了解到静态库在编译时链接,而动态库则在运行时链接,这使得动态库在节省空间和提高代码复用方面具有优势。文章还介绍了如何制作和使用动静态库,包括编译器选项和Makefile的编写,以及如何处理动态库的查找问题,例如通过修改环境变量、创建软链接或配置系统路径等方法。最后,文章深入探讨了动态库的加载机制,包括系统如何管理已加载的库和动态链接的细节,揭示了操作系统、编译器和计算机体系结构在地址空间管理中的协同工作。通过本文的阅读,读者应该能够更加深入地理解动静态库的工作原理,以及如何在实际开发中有效地应用它们。