文章目录
-
- 一、一个规范的库工程应该长什么样
- 二、头文件应该放什么
- 三、同时生成静态库和动态库
- [四、Makefile 示例](#四、Makefile 示例)
- 五、发布给别人时应该给什么
- 六、使用者如何链接你的库
- [七、常见错误 1:找不到头文件](#七、常见错误 1:找不到头文件)
- [八、常见错误 2:找不到库文件](#八、常见错误 2:找不到库文件)
- [九、常见错误 3:undefined reference](#九、常见错误 3:undefined reference)
- [十、常见错误 4:运行时报 libxxx.so not found](#十、常见错误 4:运行时报 libxxx.so not found)
- [十一、常见错误 5:链接到的库不是自己以为的那个](#十一、常见错误 5:链接到的库不是自己以为的那个)
- 十二、常用排查命令清单
-
- [1. file](#1. file)
- [2. ldd](#2. ldd)
- [3. ar -tv](#3. ar -tv)
- [4. nm](#4. nm)
- [5. readelf](#5. readelf)
- [6. objdump](#6. objdump)
- 十三、动态库版本管理简单引入
- 十四、动静态库选择建议
- 十五、专栏主线总结
- 十六、最后总结
前面 11 篇文章,我们从库的概念讲到静态库、动态库、ELF、符号重定位、进程地址空间、GOT/PIC、PLT 延迟绑定。到这里,原理主线已经打通。
最后一篇回到工程实践:一个库不只是"能编译出来"就结束了。真正可用的库,应该能被别人清晰地包含、链接、运行、排错和维护。
这篇文章把动静态库工程组织、Makefile、发布结构和常见错误排查统一梳理一遍。

一、一个规范的库工程应该长什么样
建议从一开始就把目录结构组织清楚。
例如:
text
myc/
├── include/
│ ├── my_math.h
│ └── my_string.h
├── src/
│ ├── my_math.c
│ └── my_string.c
├── test/
│ └── main.c
├── lib/
├── bin/
└── Makefile
各目录职责:
text
include/ 对外暴露的头文件
src/ 库的源码实现
test/ 测试代码
lib/ 生成的 .a/.so
bin/ 测试程序或工具
Makefile 构建入口

不要把所有文件混在一个目录里。短期看省事,长期看一定会乱。
二、头文件应该放什么
头文件是库对外的接口说明。它应该包含:
text
函数声明
必要的类型定义
必要的宏定义
接口注释
例如 include/my_math.h:
c
#pragma once
int add(int x, int y);
int sub(int x, int y);
不要在头文件里随便放内部实现细节,也不要把只在 .c 文件内部使用的辅助函数暴露出去。
库的接口越干净,使用者越容易理解,后续维护也越轻松。
三、同时生成静态库和动态库
一个库工程可以同时生成:
text
libmyc.a
libmyc.so
静态库适合独立部署,动态库适合共享和灵活升级。
源文件:
text
src/my_math.c
src/my_string.c
目标文件可以分两类:
text
普通 .o:用于静态库
-fPIC .o:用于动态库
简单项目中也可以直接用 -fPIC 编译出来的 .o 同时生成 .a 和 .so,但严格工程中可以分开管理。
四、Makefile 示例
下面是一个较完整的 Makefile 示例:
makefile
CC=gcc
AR=ar
CFLAGS=-I./include -Wall
PICFLAGS=$(CFLAGS) -fPIC
LIB_DIR=lib
BIN_DIR=bin
STATIC_LIB=$(LIB_DIR)/libmyc.a
SHARED_LIB=$(LIB_DIR)/libmyc.so
SRCS=$(wildcard src/*.c)
OBJS=$(patsubst src/%.c, %.o, $(SRCS))
PIC_OBJS=$(patsubst src/%.c, %.pic.o, $(SRCS))
all: static shared
static: $(STATIC_LIB)
shared: $(SHARED_LIB)
$(STATIC_LIB): $(OBJS)
mkdir -p $(LIB_DIR)
$(AR) rcs $@ $^
$(SHARED_LIB): $(PIC_OBJS)
mkdir -p $(LIB_DIR)
$(CC) -shared -o $@ $^
%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@
%.pic.o: src/%.c
$(CC) $(PICFLAGS) -c $< -o $@
test: all
mkdir -p $(BIN_DIR)
$(CC) test/main.c -I./include -L./lib -lmyc -Wl,-rpath=./lib -o $(BIN_DIR)/main
output: all
mkdir -p output/include output/lib
cp include/*.h output/include
cp $(STATIC_LIB) output/lib
cp $(SHARED_LIB) output/lib
tar -czf myc-release.tgz output
clean:
rm -rf *.o *.pic.o $(LIB_DIR) $(BIN_DIR) output myc-release.tgz
.PHONY: all static shared test output clean
常用命令:
bash
make static
make shared
make test
make output
make clean
五、发布给别人时应该给什么
发布库时,不应该只给一个 .a 或 .so。
至少应该包含:
text
头文件
库文件
使用说明
示例代码
版本信息
推荐发布包结构:
text
myc-release/
├── include/
│ ├── my_math.h
│ └── my_string.h
├── lib/
│ ├── libmyc.a
│ └── libmyc.so
├── examples/
│ └── main.c
└── README.md
README 中至少写清楚:
text
如何编译
如何链接静态库
如何链接动态库
动态库运行期路径如何处理
依赖哪些系统库
六、使用者如何链接你的库
静态库:
bash
gcc examples/main.c -I./include -L./lib -lmyc -o main_static
动态库:
bash
gcc examples/main.c -I./include -L./lib -lmyc -Wl,-rpath=./lib -o main_shared
或者运行前设置:
bash
export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
./main_shared
要明确告诉使用者:
text
-I 是头文件路径;
-L 是库文件路径;
-l 是库名;
rpath 或 LD_LIBRARY_PATH 影响动态库运行期查找。
七、常见错误 1:找不到头文件
错误示例:
text
fatal error: my_math.h: No such file or directory
原因:编译器找不到头文件。
检查:
bash
ls include/my_math.h
修复:
bash
gcc main.c -I./include -L./lib -lmyc -o main
核心判断:
text
这是编译阶段错误,重点检查 -I。
八、常见错误 2:找不到库文件
错误示例:
text
/usr/bin/ld: cannot find -lmyc
原因:链接器找不到 libmyc.so 或 libmyc.a。
检查:
bash
ls lib/libmyc.so
ls lib/libmyc.a
修复:
bash
gcc main.c -I./include -L./lib -lmyc -o main
核心判断:
text
这是链接阶段错误,重点检查 -L 和库文件命名。
注意库名规则:
text
-lmyc -> libmyc.so 或 libmyc.a
不要写成:
bash
-llibmyc
除非你的库文件真的叫 liblibmyc.so。
九、常见错误 3:undefined reference
错误示例:
text
undefined reference to `add'
原因:代码里调用了 add,但链接器没找到实现。
可能情况:
- 忘记写
-lmyc。 -L路径写错,实际没找到库。- 库里没有
add符号。 - 静态库链接顺序不对。
- C++ 名字修饰导致符号不匹配。
排查:
bash
nm lib/libmyc.a | grep add
nm -D lib/libmyc.so | grep add
如果库里没有 add,说明库本身没构建对。
如果库里有,但链接还失败,检查链接命令。
十、常见错误 4:运行时报 libxxx.so not found
错误示例:
text
error while loading shared libraries: libmyc.so: cannot open shared object file
或者:
bash
ldd ./main
看到:
text
libmyc.so => not found
原因:编译时找到了库,但运行时动态链接器找不到库。
解决方案:
bash
export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
或编译时加:
bash
-Wl,-rpath=./lib
或系统安装:
bash
sudo cp lib/libmyc.so /usr/local/lib/
sudo ldconfig
核心判断:
text
这是运行阶段错误,重点检查 ldd、LD_LIBRARY_PATH、rpath、ldconfig。
十一、常见错误 5:链接到的库不是自己以为的那个
有时系统里已有同名库,或者当前目录同时有 .a 和 .so,可能导致实际链接结果和预期不一致。
排查:
bash
ldd ./main
查看动态库实际路径。
查看可执行文件记录了哪些依赖:
bash
readelf -d ./main | grep NEEDED
如果你想确认静态库是否被合并进去,可以对比:
bash
file ./main
ldd ./main
nm ./main | grep add
如果是完全静态链接,ldd 可能提示:
text
not a dynamic executable
十二、常用排查命令清单
1. file
查看文件类型:
bash
file main
file lib/libmyc.a
file lib/libmyc.so
2. ldd
查看动态依赖:
bash
ldd main
3. ar -tv
查看静态库内部目标文件:
bash
ar -tv lib/libmyc.a
4. nm
查看符号:
bash
nm lib/libmyc.a
nm -D lib/libmyc.so
5. readelf
查看 ELF 信息:
bash
readelf -h main
readelf -S main
readelf -l main
readelf -s main
readelf -r main
readelf -d main
6. objdump
查看反汇编:
bash
objdump -d main
objdump -d lib/libmyc.so
十三、动态库版本管理简单引入
真实系统中的动态库经常带版本号:
text
libmyc.so
libmyc.so.1
libmyc.so.1.0.0
常见含义:
text
libmyc.so 链接时使用的名字
libmyc.so.1 SONAME,表示 ABI 主版本
libmyc.so.1.0.0 实际库文件
通常通过软链接组织:
bash
ln -s libmyc.so.1.0.0 libmyc.so.1
ln -s libmyc.so.1 libmyc.so
生成动态库时也可以指定 SONAME:
bash
gcc -shared -Wl,-soname,libmyc.so.1 -o libmyc.so.1.0.0 *.pic.o
这部分属于工程化进阶,掌握后能更好地理解系统库为什么有一堆版本软链接。
十四、动静态库选择建议
静态库适合:
text
部署环境不确定
希望程序尽量独立
工具体积不是主要问题
库版本必须固定
动态库适合:
text
多个程序共享代码
希望库能独立升级
程序体积需要控制
系统级或长期维护项目
真实工程里,不是静态库一定好,也不是动态库一定好。关键看部署方式、升级策略、运行环境和维护成本。
十五、专栏主线总结
这一整个系列从使用到原理走了一遍:
text
库的本质
-> .o + .h 的复用方式
-> 静态库制作与链接
-> 动态库制作与运行期查找
-> ELF 文件格式
-> 符号表与重定位
-> ELF 加载与进程地址空间
-> 动态库映射与共享
-> GOT/PIC
-> PLT 延迟绑定
-> 工程发布与错误排查
如果只记命令,很容易在报错时卡住;但理解了编译期、链接期、加载期、运行期分别发生什么,排查问题就会有方向。
十六、最后总结
一个可维护的库工程,至少应该做到:
- 目录结构清晰。
- 头文件和实现文件分离。
- 能自动生成
.a和.so。 - 发布包包含
include/、lib/、示例和说明。 - 明确告诉使用者如何处理动态库运行期路径。
- 出错时能用
file、ldd、nm、readelf、objdump定位问题。
到这里,Linux 下动静态库的主线就完整闭环了。