【Linux 专题】嵌入式开发必备:静态库 + 动态库创建全指南(附一键编译脚本)

0 阅读6分钟

【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

脚本使用方法

  1. 给脚本添加执行权限:

    bash

    运行

    chmod +x build.sh
    
  2. 构建静态库并编译:

    bash

    运行

    ./build.sh static
    
  3. 构建动态库并编译:

    bash

    运行

    ./build.sh dynamic
    

七、总结:嵌入式库开发核心要点

  1. 项目结构标准化:src(源码)、include(头文件)、lib(库文件)、output(可执行文件),便于维护和复用;
  2. 静态库适合资源受限设备(无运行时依赖),动态库适合需要灵活更新的场景;
  3. 动态库关键:-fPIC(位置无关代码)、-shared(生成动态库)、运行时配置库路径;
  4. 一键脚本提升效率:避免重复命令,适合日常开发和项目部署。

掌握库文件的创建和使用,能让你的嵌入式项目更规范、更灵活,比如将常用的串口驱动、传感器采集模块打包成库,多个项目直接引用即可。