【Linux 专题】嵌入式开发必备:静态库 + 动态库创建全指南(附一键编译脚本)
大家好,我是学嵌入式的小杨同学。在嵌入式 Linux 开发中,将核心模块打包成库文件(静态库.a/ 动态库.so)是必备技能 —— 既能实现代码复用(多个项目共享模块),又能隐藏核心逻辑、简化项目管理。今天就以四则运算模块为例,结合标准化项目结构(src/include/lib/output),手把手教你从 0 到 1 创建静态库和动态库,还附上一键编译脚本,直接套用即可!
一、先理清:静态库 vs 动态库的核心区别
在开始实操前,先明确两种库的差异,按需选择使用场景:
| 特性 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 链接方式 | 编译时直接嵌入可执行文件 | 运行时动态加载到内存 |
| 文件体积 | 可执行文件较大(包含库代码) | 可执行文件较小(仅含库引用) |
| 复用性 | 多个程序使用会重复嵌入,浪费内存 | 多个程序共享一份库,节省内存 |
| 更新维护 | 库更新后需重新编译程序 | 库更新后无需重新编译程序(接口兼容前提下) |
| 嵌入式适配 | 适合资源受限设备(无运行时依赖) | 适合需要灵活更新的场景(如驱动模块) |
二、标准化项目结构(直接套用)
先搭建规范的项目目录,后续所有操作基于此结构,避免文件混乱:
plaintext
calc_project/ (项目根目录)
├── 0output/ # 存放最终可执行文件
├── 1src/ # 源代码目录(.c文件)
│ ├── Add.c # 加法模块
│ ├── div.c # 除法模块
│ ├── mul.c # 乘法模块
│ ├── sub.c # 减法模块
│ ├── utils.c # 工具函数(如参数校验)
│ └── main.c # 主函数(解析参数+调用库函数)
├── 2include/ # 头文件目录(.h文件)
│ ├── Add.h # 加法函数声明
│ ├── Div.h # 除法函数声明
│ ├── mul.h # 乘法函数声明
│ ├── sub.h # 减法函数声明
│ ├── utils.h # 工具函数声明
│ └── calc.h # 汇总头文件(方便主函数引用)
└── 3lib/ # 库文件目录(.o目标文件、.a/.so库文件)
补充核心头文件(2include/calc.h)
为了简化主函数引用,在2include目录下创建汇总头文件,无需逐个引入单个模块头文件:
c
运行
#ifndef CALC_H
#define CALC_H
// 引入所有模块头文件
#include "Add.h"
#include "Div.h"
#include "mul.h"
#include "sub.h"
#include "utils.h"
#endif
三、实操 1:创建静态库(libcalc.a)
静态库是 “编译时链接”,步骤分为 “生成目标文件→打包静态库→链接生成可执行文件”。
1. 生成目标文件(.o)
进入源代码目录,将所有模块编译为目标文件(仅编译不链接):
bash
运行
# 1. 进入1src目录
cd 1src
# 2. 编译所有.c文件为.o文件,指定头文件路径为../2include
gcc -c Add.c div.c mul.c sub.c utils.c -I../2include
# 3. 将生成的.o文件移动到3lib目录(避免污染源代码目录)
mv *.o ../3lib
- 关键参数:
-c(仅编译不链接)、-I../2include(指定头文件搜索路径)。
2. 打包目标文件为静态库(.a)
进入 3lib 目录,用ar命令将目标文件打包为静态库(库名必须以lib开头):
bash
运行
# 1. 进入3lib目录
cd ../3lib
# 2. 打包所有.o文件为静态库libcalc.a
ar crsv libcalc.a Add.o div.o mul.o sub.o utils.o
- 命令解析:
ar是静态库打包工具,c(创建库)、r(替换旧文件)、s(生成索引)、v(显示详细过程)。
3. 链接静态库生成可执行文件
回到项目根目录,编译主函数并链接静态库,生成最终可执行文件:
bash
运行
# 1. 回到项目根目录
cd ..
# 2. 编译main.c,链接静态库,输出到0output/main
gcc -o 0output/main 1src/main.c -L3lib -lcalc -I2include
- 关键参数:
-L3lib(指定库文件搜索路径)、-lcalc(链接 libcalc.a,自动补全lib前缀和.a后缀)、-I2include(指定头文件路径)。
4. 运行程序
直接执行 0output 目录下的可执行文件,验证静态库是否生效:
bash
运行
./0output/main 10 + 5 # 调用加法模块,输出15
./0output/main 20 * 3 # 调用乘法模块,输出60
四、实操 2:创建动态库(libcalc.so)
动态库是 “运行时链接”,步骤与静态库类似,但需生成 “位置无关代码”,且运行时需指定库路径。
1. 生成位置无关目标文件(.o)
动态库要求目标文件是 “位置无关代码”(可在内存任意地址加载),编译时添加-fPIC参数:
bash
运行
# 1. 进入1src目录
cd 1src
# 2. 编译为位置无关目标文件,指定头文件路径
gcc -fPIC -c Add.c div.c mul.c sub.c utils.c -I../2include
# 3. 移动.o文件到3lib目录
mv *.o ../3lib
- 关键参数:
-fPIC(生成位置无关代码,动态库必备)。
2. 生成动态库(.so)
进入 3lib 目录,用gcc -shared命令将目标文件打包为动态库(库名必须以lib开头,后缀.so):
bash
运行
# 1. 进入3lib目录
cd ../3lib
# 2. 生成动态库libcalc.so
gcc -shared -o libcalc.so Add.o div.o mul.o sub.o utils.o
- 关键参数:
-shared(指定生成动态库)。
3. 链接动态库生成可执行文件
与静态库链接命令完全一致,编译器会自动区分静态库和动态库:
bash
运行
# 1. 回到项目根目录
cd ..
# 2. 编译main.c,链接动态库,输出到0output/main
gcc -o 0output/main 1src/main.c -L3lib -lcalc -I2include
4. 运行程序(动态库路径配置)
动态库运行时需让系统找到它,否则会报错 “找不到库文件”,提供两种解决方案:
方式 1:临时生效(当前终端)
适合开发测试,仅当前终端有效:
bash
运行
# 1. 指定动态库搜索路径(临时添加到环境变量)
export LD_LIBRARY_PATH=./3lib:$LD_LIBRARY_PATH
# 2. 运行程序
./0output/main 15 / 3 # 输出5
方式 2:永久生效(推荐)
将动态库复制到系统默认库目录(/usr/lib),所有终端均可使用:
bash
运行
# 1. 复制动态库到系统库目录(需sudo权限)
sudo cp 3lib/libcalc.so /usr/lib
# 2. 运行程序(无需再配置路径)
./0output/main 8 - 2 # 输出6
五、避坑指南:常见错误解决
1. 报错 “与../3lib/xxx.o 为同一文件”
- 原因:在 3lib 目录下执行了
mv *.o ../3lib,源路径和目标路径相同; - 解决:回到 1src 目录编译生成.o 文件,再移动到 3lib(严格按步骤操作)。
2. 动态库运行报错 “error while loading shared libraries”
- 原因:系统找不到动态库;
- 解决:按上述 “动态库路径配置” 操作,或在链接时指定
-Wl,-rpath=./3lib(嵌入库路径到可执行文件)。
3. 编译时 “找不到头文件”
- 原因:未指定头文件路径,或路径错误;
- 解决:确保编译命令中添加
-I2include(头文件在项目根目录的 2include 目录)。
六、福利:一键编译脚本(build.sh)
为了避免重复输入命令,编写一键编译脚本,支持静态库 / 动态库快速构建,直接放在项目根目录:
bash
运行
#!/bin/bash
# 一键编译脚本:支持静态库(static)和动态库(dynamic)
# 使用方式:./build.sh static 或 ./build.sh dynamic
# 检查参数
if [ $# -ne 1 ]; then
echo "用法:./build.sh static(静态库) 或 ./build.sh dynamic(动态库)"
exit 1
fi
# 创建必要目录(若不存在)
mkdir -p 0output 3lib
# 编译目标文件
echo "=== 生成目标文件 ==="
cd 1src
if [ "$1" = "dynamic" ]; then
# 动态库:生成位置无关代码
gcc -fPIC -c Add.c div.c mul.c sub.c utils.c -I../2include
else
# 静态库:普通目标文件
gcc -c Add.c div.c mul.c sub.c utils.c -I../2include
fi
mv *.o ../3lib
cd ..
# 打包库文件
echo "=== 打包库文件 ==="
cd 3lib
if [ "$1" = "dynamic" ]; then
# 生成动态库
gcc -shared -o libcalc.so Add.o div.o mul.c sub.o utils.o
else
# 生成静态库
ar crsv libcalc.a Add.o div.o mul.o sub.o utils.o
fi
cd ..
# 链接生成可执行文件
echo "=== 生成可执行文件 ==="
gcc -o 0output/main 1src/main.c -L3lib -lcalc -I2include
# 提示运行方式
echo "=== 编译完成 ==="
if [ "$1" = "dynamic" ]; then
echo "运行方式:export LD_LIBRARY_PATH=./3lib:$LD_LIBRARY_PATH && ./0output/main"
else
echo "运行方式:./0output/main"
fi
脚本使用方法
-
给脚本添加执行权限:
bash
运行
chmod +x build.sh -
构建静态库并编译:
bash
运行
./build.sh static -
构建动态库并编译:
bash
运行
./build.sh dynamic
七、总结:嵌入式库开发核心要点
- 项目结构标准化:
src(源码)、include(头文件)、lib(库文件)、output(可执行文件),便于维护和复用; - 静态库适合资源受限设备(无运行时依赖),动态库适合需要灵活更新的场景;
- 动态库关键:
-fPIC(位置无关代码)、-shared(生成动态库)、运行时配置库路径; - 一键脚本提升效率:避免重复命令,适合日常开发和项目部署。
掌握库文件的创建和使用,能让你的嵌入式项目更规范、更灵活,比如将常用的串口驱动、传感器采集模块打包成库,多个项目直接引用即可。