目录
(对于一个新的认知,是不可能建立在抽象上的,所以必须具体。我会先带着你看现象,然后再去解释,这是什么,为什么,怎么办。)
本文章,将介绍:
什么是静态库?
动态库如何写静态库和动态库?
第三方库是什么?
怎么用调库?等
一、静态库
查找文件依赖的库:
ldd
Linux下,以.so结尾为动态库、.a为静态库
1、制作静态库
第一步,写一个.h的头文件,头文件内写有函数声明
第二步,写.c文件,.c文件内写有对应头文件中函数的实现
第三步,编译.c文件,得到.o文件
第四步,制作静态库,使用ar -rc命令(-rc:replace and create)
(.o 可重定位目标文件(目标文件)
.h头文件是一个手册,提供函数声明,告诉用户怎么用
.o文件提供实现,只需提供main函数,调用实现的方法,再和.o文件链接就行了)
示例:(将file1.o file2.o两个目标文件,打包成为libmylib.a静态库)
ar -rc libmylib.a file1.o file2.o
为了制作一个简单的 ADD
静态库,下面提供了头文件和实现代码,以及如何创建静态库的步骤。
1. 头文件 (add.h
)
// 函数声明
int add(int a, int b);
(为了简单,我们去掉条件编译)
2. 实现文件 (add.c
)
// add.c
#include "add.h"
// 函数实现
int add(int a, int b) {
return a + b;
}
3. 编译源文件为对象文件
首先,使用 gcc
编译源文件 add.c
为对象文件 add.o
:
gcc -c add.c
4. 创建静态库
ar -rc libadd.a add.o
5. 使用静态库
编写一个测试程序来使用这个静态库,例如 main.c
:
// main.c
#include <stdio.h>
#include "add.h"
int main() {
int result = add(3, 5);
printf("Result of add(3, 5): %d\n", result);
return 0;
}
6. 编译并链接测试程序
将测试程序 main.c
与静态库 libadd.a
链接:
gcc main.c -L. -ladd -o test_program
-L.
:指定当前目录作为库文件的搜索路径。-ladd
:链接libadd.a
库(lib
前缀和.a
后缀可以省略)。
7. 运行测试程序
运行编译好的程序来验证静态库是否工作正常:
./test_program
2、静态库是什么?
所谓的库文件,本质就是把.o文件打包
3、为什么有库?
提高开发效率。
例如说,程序员的第一个代码:“Hello,World!”,向屏幕输出。你要做的就是,写上头文件,然后待用一个叫做printf的函数,然后对应格式写好,然后run即可。这个期间你做了什么?编译、运行、执行、打印、二进制、汇编等等所有的工作你什么都没有做,你就是一个计算机小白,啥也不会,你只是打了几个字,然后键盘按下Ctrl+F5,然后over。没了。你做的工作,只是打了几个字。打字,你告诉幼儿园小朋友,先按键盘的这个,再按键盘的哪个,最后按这个.......小孩都会编程。简直就是小孩玩泥巴。所以,这让我们站在巨人的肩膀上去看世界,改变世界。降低我们的学习成本。这就是为什么会有库的原因。
4、静态库编译
库一般以lib开头,后面的才是真正的名字,例如:linbc.so.6就是标准C语言的库,其实叫做c.so
不建议把静态库下载到我们的操作系统上
那么怎么办?
要使用-L(Link),-I(include)三个命令:
选项 | 作用 | 用法示例 | 说明 |
---|---|---|---|
-L |
指定库文件所在的目录 | gcc -o myprogram main.c -L/usr/local/lib -lmylib |
指定库文件的搜索路径,链接时查找 libmylib.a 或 libmylib.so |
-I |
指定头文件的搜索目录 | gcc -o myprogram main.c -I/usr/local/include |
指定头文件的搜索路径,查找 main.c 中包含的头文件 |
头文件中:
“”双引号的意思:在用户自己写的头文件查找
<>:在系统头文件查找系统中的库路径:头文件路径:usr/include、文件路径:/usr/lib64
示例:-L表示头文件路径、-I表示库文件路径
gcc -o myprogram main.c -L/usr/local/lib -lmylib -I/usr/local/include
二、动态库
1、制作动态库
以.so结尾
形成动态库:直接使用gcc(gcc在不使用static选项时,默认使用动态库)
gcc -shared -o libmylib.so file1.o file2.o
动态库:因为动态库要在程序运行的时候,找到动态库加载并运行
为什么静态库没有这个问题呢?
这是因为静态库在编译期间,已经将库的代码拷贝到我们的可执行程序内部
运行和加载,就和库没有关系了
当我们自己写了一个动态库时,
我们告诉gcc编译器动态库的文件路径
于是,形成了可执行程序
但是,运行可执行程序却出问题
会显示,找不到动态库
这是为什么?
因为,可执行程序的执行,是由操作系统完成
你并没有将动态库的文件路径位置告诉操作系统
所以,当操作系统运行可执行程序时
找不到动态库,那怎么办呢?
2、添加动态库
方法 | 说明 | 示例命令/步骤 |
---|---|---|
1. 将动态库复制到系统目录 | 将动态库文件复制到系统的动态库目录,如 /usr/lib 或 /usr/local/lib 。 |
sudo cp libmylib.so /usr/local/lib |
2. 建立软链接 | 在系统动态库目录中创建库文件的软链接。 | sudo ln -s /usr/local/lib/libmylib.so /usr/lib/libmylib.so |
3. 使用 LD_LIBRARY_PATH |
临时添加动态库路径到环境变量 LD_LIBRARY_PATH 。 |
export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH |
4. 永久添加到 .bashrc |
将 LD_LIBRARY_PATH 设置添加到用户的 .bashrc 文件中(不推荐)。 |
echo export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH' >> ~/.bashrc<br>source ~/.bashrc |
5. 更新 /etc/ld.so.conf.d |
在 /etc/ld.so.conf.d 目录下创建配置文件并使用 ldconfig 更新库缓存。 |
echo '/path/to/library' |
1、将动态库cp到系统的动态库中
2、建立软链接
3、LD_LIBRARY_PAYH,环境变量方式添加
library:库
4、永久添加:到~家目录的.bashrc文件中添加(不推荐)
5、/etc/ld.so.conf.d 新增动态库搜索的配置文件,ldconfig命令生效
三、-static
当文件编译时,默认链接的是动态库;如果没有使用-static,并且只提供.a,只能静态链接当前的.a库,其他的库正常动态链接
-static的意义是什么?必须强制进行静态链接
这需要保证必须提供对应的静态库
四、第三方库
第三方库,其实就是别人写的库。根据上述的了解,大家应该可以自行掌握第三方库了。
如果用第三方库:用头文件+库文件
推荐第三方库:ncurses(超详细) ncurses 库使用介绍: 实现终端 GUI - 四季夏目天下第一 - 博客园 (cnblogs.com)
五、动态库在内存的管理
动态库加载:可执行程序 和 地址空间
动态库在加载后要映射到当前进程的地址空间中的堆、栈之间的共享区
动态库首先会加载到内存中
然后,哪一个进程用到动态库
就会在页表形成和动态库地址的映射
映射的一端是介于堆和栈之间的共享区
另一端是动态库的内存地址
而动态库在内存中只需要加载一份
当其他进程也用到该动态库时
这个就会相应的,通过页表,加载动态库内存地址和进程的共享区之间的映射
以此类推,再多的进程也是如此
因此,动态库也叫做共享库
我们的可执行程序,编译成功,没有加载运行,二进制代码有“地址”吗?
有地址
可执行和程序编译之后,会变成很多汇编语句,每条汇编语句都会有他自己的地址
反汇编命令:objdump -d binary_file
如何编制?
全0到全F编址,叫做平坦模式
于是,可执行文件记录了所有的代码的地址信息
这些地址叫做逻辑地址,其实也等价于虚拟地址
同时,也就有了各个数据段的开始位置和结束位置
于是,当运行程序,创建程序时
可执行文件内的所编译的数据就可以用来初始化进程的属性结构体mm_struct
同时,可执行文件的虚拟地址和和物理地址也建立起映射关系
虚拟地址空间,不仅仅是操作系统独有
虚拟地址空间是一个标准
这个标准加载器、编译器也要遵守
在CPU内,有一个PC寄存器
PC指向正在执行指令的下一条指令
PC指向哪个位置,CPU就执行那里的代码
当CPU开始执行代码时
PC会指向可执行文件的虚拟地址
再通过虚拟地址找到对应物理地址
于是,整个程序就这样运转起来
库加载到内存任意位置都可以正常运行
因为库内部的各个函数部分,是偏移量
偏移量不变,所以加上库的起始地址就可以访问对应函数
所以,库加载到不同的内存位置,本质是改变库的起始位置
所以本质上,库函数调用,其实也是在我的地址空间内来回跳转
操作系统内会有很多被加载的库
所以需要管理
怎么管理?
先描述、再组织
库也是文件
也要创建结构体管理
所以,会有一个维护库结构体的链表