一、库文件介绍
• 定义:库文件是一组预先编译好的函数、类或数据的集合,以文件的形式存在,用于被其他程序调用;
• 本质:库文件是一种可执行代码的二进制形式,是代码复用的一种方式,将常用功能封装起来,方便 开发者在不同项目中使用;
• 作用:
• 提高开发效率:避免重复编写相同功能的代码,减少开发时间;
• 便于代码维护:集中管理功能模块,修改和升级库时,只需更新库文件,无需修改使用库的程序;
• 保护代码隐私:可以将核心代码封装在库中,只提供接口供外部使用,隐藏实现细节;
库的分类:静态库和共享库(动态库) 在不同平台中都存在大量的库,不同平台(如 Linux vs Windows)的指令集、二进制格式、系统调用不同, 导致库无法跨平台直接使用(比如 Linux 的 .so 不能在 Windows 上运行);
• linux平台下库文件命名:
• 静态库命名规则为:libxxx.a(以lib为前缀+自定义库名+.a后缀);
• 动态库命名规则为:libxxx.so(以lib为前缀+自定义库名+.so后缀);
若涉及到版本管理,动态库命名可扩展为:libxxx.so.x.y.z x 为主版本号,不兼容旧版;y 为次版本号,兼容旧版;z 为发行版本号,修复问题不影响接口
二、静态库的介绍和制作
1.静态库介绍
定义:静态库是在程序编译链接阶段,其代码会被完整地"复制"到最终生成的可执行文件中,成为可执行 文件的一部分,编译完成后,静态库与可执行文件"绑定",运行时无需额外依赖;
• 特点:
• 可执行文件独立运行,不依赖外部库文件;
• 静态库会使可执行文件体积较大,但是效率比较高;
• 若静态库更新,需重新编译链接程序;
• 静态库文件格式: • windows平台下为.lib;Linux平台下为.a;Mac平台下为.a;
2.制作流程
• 准备源文件:编写实现库功能的源文件(如 .c或 .cpp);
• 编译生成目标文件:使用编译器将源文件编译成目标文件(如 .o或.obj);
示例:gcc -c 源文件.c -o 目标文件.o
• 打包生成静态库:使用归档工具将目标文件打包成静态库;
示例:ar -cr 静态库.a 目标文件.o
•编译主程序并链接静态库,生成可执行文件;
示例:gcc 目标文件.o -l库名 -L库路径 -o 可执行文件
说明:如果库名是libmyadd.a,则库名是myadd,编译器会自动拼接lib前缀和.a后缀进行查找库文件;
3.核心选项
gcc常用选项
| 选项 | 作用 | 示例命令 |
|---|---|---|
-c |
只编译不链接,生成目标文件(.o) |
gcc -c add.c -o add.o |
-o |
指定输出文件名称 | gcc -c main.c -o main.o |
-l |
指定要链接的库名(省略 lib 前缀和 .a/.so 后缀) |
gcc main.c -lmyadd -o main |
-L |
指定库文件的搜索路径 | gcc main.c -L./lib -lmyadd -o main |
ar工具常用选项
| 选项 | 作用 | 示例命令 |
|---|---|---|
-c |
创建新的归档文件(静态库 .a) |
ar -cr libmyadd.a add.o |
-r |
将目标文件添加到归档文件中(替换已存在的同名文件) | ar -cr libmyadd.a add.o |
4.制作并使用静态库示例代码
cs
// add.h
#ifndef ADD_H
#define ADD_H
int add(int a, int b);
#endif
// add.c
#include "add.h"
int add(int a, int b) {
return a + b;
}
// main.c
#include <stdio.h>
#include "add.h"
int main() {
printf("1 + 2 = %d\n", add(1, 2));
return 0;
}
三、动态库的介绍和制作
1.动态库介绍
定义:
动态库也称之为共享库,是在程序运行时才被加载到内存中,可以被多个可执行文件共享使用, 为它们提供函数、数据等资源;
• 特点:
• 可执行文件运行时,需依赖系统中存在的动态库;
• 可执行文件体积小,有效节省磁盘和内存空间;
• 更新灵活,只要接口不变,无需重新编译依赖它的可执行文件;
• 动态库文件格式: • windows平台下为.dll;Linux平台下为.so;Mac平台下为.dylib;
2.制作流程
• 准备源文件:编写实现库功能的源文件(如 .c或 .cpp);
•编译生成与位置无关的目标文件: 使用编译器编译源文件时,添加相关选项(如GCC的-fPIC选项)生成位置无关代码(PIC)的目标文件; 示例:gcc -c -fPIC 源文件.c -o 目标文件.o
• 链接生成动态库:使用编译器将目标文件链接为动态库,多个程序可共享同一份动态库文件,节省内存;
示例:gcc -shared 目标文件.o -o 动态库.so
• 编译时通过-L指定路径、-l指定库名生成可执行文件; 示例:gcc 源文件.c -l库名 -L库路径 -o 可执行文件
3.Linux 系统中,动态库(.so 文件)的三种运行时加载方法
方法 1:临时设置 LD_LIBRARY_PATH 环境变量
这是最简单、最常用的临时方法,适合调试和测试。
bash
bash
运行
# 将当前目录加入动态库搜索路径
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
$PWD:表示当前工作目录,也可以换成你的库文件所在路径(比如 ./lib)。
特点:
临时生效,只对当前终端会话有效,关闭终端后失效。
适合开发调试,不会影响系统全局配置。
方法 2:修改 /etc/ld.so.conf 系统配置文件
这是永久生效的全局配置方法,所有用户和程序都能使用。
编辑配置文件,添加库路径:
bash
bash
运行
sudo vim /etc/ld.so.conf
在文件中添加你的动态库路径,比如:
bash
text
/home/hqyj/dynamic_library
更新系统缓存,让配置生效:
bash
bash
运行
sudo ldconfig
特点:
永久生效,重启后依然有效。
配置一次后,所有程序都能找到这个路径下的动态库。
方法3 将动态库放到系统标准路径
这是最 "正统" 的方法,把库文件放到系统默认的库目录里。
-系统级库:放在 /usr/lib
-用户自定义库:放在 /usr/local/lib
bash
# 示例:将 libmysub.so 复制到 /usr/local/lib
sudo cp libmysub.so /usr/local/lib
# 更新系统缓存
sudo ldconfig
特点:
永久生效,系统启动时就会加载这些路径下的库。
适合安装第三方库,不推荐频繁修改,避免污染系统目录。
对比
| 方法 | 生效范围 | 永久 / 临时 | 适用场景 |
|---|---|---|---|
LD_LIBRARY_PATH |
当前终端会话 | 临时 | 开发调试、临时运行程序 |
修改 /etc/ld.so.conf |
系统全局 | 永久 | 长期使用的库、需要所有用户访问 |
放到 /usr/lib 或 /usr/local/lib |
系统全局 | 永久 | 安装第三方库、系统级依赖 |
实操建议
开发调试阶段:优先用 export LD_LIBRARY_PATH=PWD:LD_LIBRARY_PATH,简单又不会影响系统。
长期部署:用修改 /etc/ld.so.conf 或放到 /usr/local/lib 的方法,保证程序在任何环境下都能正常运行。
4.核心选项
a.GCC 动态库制作与使用命令速查表
| 阶段 | 工具 | 选项 | 作用 | 示例 |
|---|---|---|---|---|
| 编译阶段 | gcc |
-c |
只编译不链接,生成目标文件 .o |
gcc -c add.c -o add.o |
-fPIC |
生成位置无关代码,动态库必需 | gcc -c -fPIC add.c -o add.o |
||
-o |
指定输出文件名(目标文件或库) | gcc -c -fPIC add.c -o add.o |
||
| 链接阶段 | gcc |
-shared |
将 .o 链接为动态库 .so |
gcc -shared add.o -o libmyadd.so |
| 使用阶段 | gcc |
-L |
指定动态库的搜索路径 | gcc main.c -lmyadd -L./lib -o main |
-l |
指定要链接的动态库名(省略 lib 和 .so) |
gcc main.c -lmyadd -L. -o main |
||
| 系统工具 | ldconfig |
- | 更新系统动态库缓存 | sudo ldconfig |
b.关键概念深度解析
1. -fPIC 是什么?
全称:Position-Independent Code(位置无关代码)
作用:生成的目标文件中,所有地址都是相对地址,这样动态库可以被加载到内存的任意位置运行,而不是固定在某个地址。
注意:制作动态库时必须加 -fPIC,否则生成的 .o 文件无法链接成动态库。
2. -shared 选项
作用:告诉 GCC 生成动态库,而不是可执行文件。
配合 -o 使用,指定输出的库文件名,格式一般为 libxxx.so(lib 前缀是约定俗成的)。
3. -L 和 -l 的区别
-L:告诉编译器在哪个目录找库文件。
-l:告诉编译器要链接哪个库文件,库名是去掉 lib 前缀和 .so 后缀的部分。
例如 libmyadd.so,链接时写 -lmyadd。
c.示例
cs
// add.h
#ifndef ADD_H
#define ADD_H
int add(int a, int b);
#endif
// add.c
#include "add.h"
int add(int a, int b) {
return a + b;
}
// main.c
#include <stdio.h>
#include "add.h"
int main() {
printf("1 + 2 = %d\n", add(1, 2));
return 0;
}
- 编译阶段(生成带 -fPIC 的目标文件)
bash
bash
运行
gcc -c -fPIC add.c -o add.o
- 链接阶段(生成动态库)
bash
bash
运行
gcc -shared add.o -o libmyadd.so
- 使用阶段(编译主程序并链接动态库)
bash
bash
运行
gcc main.c -L. -lmyadd -o main
- 运行阶段(让系统找到动态库)
bash
# 临时设置环境变量(开发调试用)
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
./main
d.ldconfig 命令
作用:更新系统动态库缓存,让系统能识别新添加的动态库。
使用场景:当你把 .so 文件放到 /usr/lib 或 /usr/local/lib 等系统路径后,需要执行 sudo ldconfig 让系统识别它。
四、动静态库的区别
1.核心区别
静态库(.a):编译时直接打包进可执行文件
动态库(.so):运行时才加载,不打包进程序
2.对比表
| 对比项 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 后缀名 | .a(Linux) |
.so(Linux) |
| 加载时机 | 编译链接时 | 程序运行时 |
| 是否打包进程序 | ✅ 是,全部复制进去 | ❌ 否,只留引用 |
| 可执行文件大小 | 大 | 小 |
| 运行时依赖库 | ❌ 不依赖,可直接运行 | ✅ 必须依赖,找不到就报错 |
| 更新库文件 | 必须重新编译程序 | 直接替换库,程序不用重新编译 |
| 内存占用 | 多个程序用同一个库,会加载多份 | 多个程序共享同一份,省内存 |
| 制作命令 | ar cr libxxx.a x.o |
gcc -shared -fPIC -o libxxx.so x.o |
| 运行速度 | 稍快(无加载开销) | 稍慢(运行时加载) |
最关键的 4 个核心区别
- 打包方式不同
静态库:代码直接塞进你的可执行文件
动态库:代码留在外面,程序运行时去 "借" 用
- 更新方式不同
静态库:库改了 → 程序必须重新编译
动态库:库改了 → 直接替换 .so 文件,程序不用动
- 内存占用不同
静态库:10 个程序用 → 10 份库代码
动态库:10 个程序用 → 内存只存 1 份,大家共享
- 运行依赖不同
静态库:发给别人就能直接跑
动态库:必须保证对方机器上有这个 .so 库
3.怎么选择用哪个?
✔ 用 静态库
不想让程序依赖外部文件
程序要发给别人直接运行
小工具、简单程序
✔ 用 动态库
库可能经常更新
多个程序共用同一个库(节省空间)
系统级大型程序、驱动、插件
4.制作命令对照表
静态库
bash
gcc -c add.c -o add.o
ar cr libadd.a add.o
gcc main.c -L. -ladd -o main
*\编译源文件生成目标文件:
gcc -c 源文件.c -o 目标文件.o
(例:gcc -c add.c -o add.o)
使用 ar 命令创建静态库:
ar crs 静态库名.a 目标文件.o
(例:ar crs libmyadd.a add.o)
编译程序时链接静态库:
gcc 主程序.c -L库路径 -l库名 -o 可执行文件
(例:gcc main.c -L. -lmyadd -o main)
执行程序(无需额外配置):
./可执行文件
\*
动态库
bash
gcc -c -fPIC add.c -o add.o
gcc -shared -o libadd.so add.o
gcc main.c -L. -ladd -o main
export LD_LIBRARY_PATH=./
./main
* 编译源文件生成位置无关目标文件:
gcc -c -fPIC 源文件.c -o 目标文件.o
(例:gcc -c -fPIC add.c -o add.o)
使用 gcc 创建动态库:
gcc -shared 目标文件.o -o 动态库名.so
(例:gcc -shared add.o -o libmyadd.so)
编译程序时链接动态库:
gcc 主程序.c -L库路径 -l库名 -o 可执行文件
(例:gcc main.c -L. -lmyadd -o main)
设置临时环境变量(运行必须):
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
执行程序:
./可执行文件
*
5.对照
bash
======== 静态库流程 ========
gcc -c add.c -o add.o
ar crs libmyadd.a add.o
gcc main.c -L. -lmyadd -o main
./main
======== 动态库流程 ========
gcc -c -fPIC add.c -o add.o
gcc -shared add.o -o libmyadd.so
gcc main.c -L. -lmyadd -o main
export LD_LIBRARY_PATH=./
./main
五、库制作常见问题及解决方案
方法 1:拷贝库到系统目录(/usr/lib或/lib)
原理:系统默认搜索这些目录;
优点:简单直接,无需额外配置;
缺点:可能污染系统库,不适合多版本管理;
方法 2:设置LD_LIBRARY_PATH环境变量
原理:告诉系统额外的库搜索路径;
优点:灵活,可临时指定路径;
缺点:仅当前终端有效,关闭后失效;
方法 3:修改/etc/ld.so.conf并执行ldconfig
原理:将库路径写入配置文件,刷新系统库缓存;
优点:永久生效,支持目录下所有库;
缺点:需管理员权限,修改后需刷新缓存;
bash
* 静态库常见问题:
1. undefined reference → 顺序错误,库放最后
2. cannot find -lxxx → 库名必须 libxxx.a,加 -L.
* 动态库常见问题:
1. 编译失败 → 缺少 -fPIC
2. 运行失败 → 设置 LD_LIBRARY_PATH=./
3. 找不到库 → 永久配置 /etc/ld.so.conf
*
找不到库 → 看名字是否 libxxx.a/so
链接报错 → 库必须放命令最后
动态库不能运行 → 加 export LD_LIBRARY_PATH=./
动态库编译失败 → 加 -fPIC