1. 什么是库
库是写好的、现有的、成熟的、可以复用的代码。每个程序都依赖很多底层库,库的存在使得开发不必从零开始。
本质:库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
库的两种类型:
-
静态库 :
.a(Linux)、.lib(Windows) -
动态库 :
.so(Linux)、.dll(Windows)
2. 静态库
2.1 特点
-
静态库(
.a)在程序编译链接时 把库的代码直接拷贝到可执行文件中。 -
程序运行时不再需要静态库。
-
可执行文件体积较大,但部署简单(不依赖外部库)。
2.2 静态库的本质
库的本质 :无论是静态库还是动态库,其基础都是源文件编译后生成的
.o目标文件。静态库(.a)就是多个.o文件打包成一个归档文件。使用gcc/g++链接时,链接器会从归档中提取需要的模块合并到最终可执行文件中。
静态库中不包含 main 函数,库只提供可复用的功能模块,不包含程序入口。
2.3 制作静态库
使用 ar(GNU归档工具)将多个 .o 文件打包成 .a 文件。
示例 :将 my_stdio.c 和 my_string.c 封装成静态库 libmystdio.a。
Makefile 片段:
makefile
libmystdio.a: my_stdio.o my_string.o
ar -rc $@ $^
%.o: %.c
gcc -c $<
-
ar -rc:r表示插入或替换文件,c表示创建归档文件。 -
查看静态库内容:
ar -tv libmystdio.a(t列出文件,v显示详细信息)。
2.4 使用静态库
编译时需指定头文件路径、库路径和库名。
-
头文件搜索路径 :
-I选项 -
库文件搜索路径 :
-L选项 -
库名 :
-l选项(去掉前缀lib和后缀.a/.so)
使用场景示例:
bash
# 场景1:头文件和库文件已安装到系统路径(如 /usr/lib, /usr/include)
$ gcc main.c -lmystdio
# 场景2:头文件和库文件在当前目录
$ gcc main.c -L. -lmystdio
# 场景3:头文件和库文件在独立路径
$ gcc main.c -I头文件路径 -L库文件路径 -lmystdio
注意:静态库链接后,可执行文件不再依赖该库,即使删除静态库,程序仍可运行。
3. 动态库
3.1 特点
-
动态库(
.so)在程序运行时才被加载到内存,多个程序可以共享内存中的同一份库代码。 -
可执行文件只包含函数入口地址的引用,体积小。
-
节省磁盘和内存空间,但部署时需要确保运行时能找到动态库。
库:应用程序 = 1:n(一份物理内存中的动态库可被多个进程共享)。
3.2 制作动态库
生成位置无关码(-fPIC)并链接为共享库(-shared)。
Makefile 片段:
libmystdio.so: my_stdio.o my_string.o
gcc -o $@ $^ -shared
%.o: %.c
gcc -fPIC -c $<
-
-fPIC:生成位置无关代码,使得动态库可以被加载到任意内存地址。 -
-shared:生成共享库。
3.3 使用动态库
编译命令与静态库类似:
bash
$ gcc main.c -L. -lmystdio
生成的可执行文件 a.out 在运行时需要找到 libmystdio.so。
3.4 运行时动态库查找问题
使用 ldd a.out 查看可执行文件依赖的共享库:
bash
$ ldd a.out
linux-vdso.so.1 => (0x...)
libmystdio.so => not found
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2
出现 not found 表示运行时找不到动态库。
关键理解 :编译时用
-L和-l只告诉了编译器(gcc)库的位置,但操作系统(动态链接器)并不知道。系统 != gcc,运行时需要另外告诉系统库的路径。
3.5 解决方案
-
拷贝库到系统库路径
如
/usr/lib、/usr/local/lib、/lib64等(需 root 权限)。 -
创建软链接
在系统库路径下创建同名软链接指向实际库文件。
-
设置环境变量
LD_LIBRARY_PATH
bash
export LD_LIBRARY_PATH=库所在目录:$LD_LIBRARY_PATH
- 配置
/etc/ld.so.conf.d/并运行ldconfig
在/etc/ld.so.conf.d/下创建一个.conf文件,写入库路径,然后运行ldconfig更新缓存。
bash
$ sudo cat /etc/ld.so.conf.d/bit.conf
/root/tools/linux
$ sudo ldconfig
4. 编译链接的默认行为
-
结论1 :
gcc/g++默认使用动态库。如果同时存在同名静态库和动态库,优先链接动态库。 -
结论2 :若想强制静态链接,需加
-static选项,此时必须存在对应的静态库。如果只存在静态库,则只能静态链接。 -
结论3 :Linux 系统默认安装的库通常优先提供动态库(
.so),静态库(.a)可能需要单独安装(如-devel包)。
5. 可执行程序的加载过程(简述)
-
操作系统读取可执行文件的头部信息,获取程序入口地址。
-
根据文件中的段信息,将代码段、数据段等映射到虚拟内存的合适位置。
-
对于动态链接的可执行文件,还需加载动态链接器 (如
/lib64/ld-linux-x86-64.so.2)。 -
动态链接器负责加载所需的共享库(
.so),完成符号重定位(将库函数调用地址修正为实际内存地址)。 -
最后跳转到程序入口开始执行。
6. 虚拟地址空间与动态库加载
-
每个进程拥有独立的虚拟地址空间(例如 32 位系统为 4GB,其中内核占用一部分)。
-
动态库被加载到虚拟地址空间的共享区域,通过页表映射到物理内存。
-
由于使用了位置无关代码 (
-fPIC),动态库可以被加载到任意地址而不需修改代码,从而实现多个进程共享同一份物理内存中的库。
7. 总结
-
静态库 :链接时合并到可执行文件,运行时独立,生成方式
ar -rc。 -
动态库 :运行时加载,节省资源,生成方式
gcc -shared -fPIC。 -
库的查找 :编译时用
-L和-l,运行时依赖动态链接器搜索路径(系统目录、LD_LIBRARY_PATH、ldconfig配置)。 -
加载过程:操作系统将程序映射到虚拟内存,动态链接器完成共享库的加载和符号重定位。
-
虚拟内存:使动态库能在物理内存中只保留一份副本,被多个进程共享。