📌 前言

在Linux系统开发中,库文件是代码复用的核心手段。无论你是做嵌入式开发、驱动开发,还是应用层开发,掌握库的制作与使用都是必备技能。本文将详细讲解静态库(.a)和动态库(.so)的制作流程、命令详解以及最佳实践。


一、库的基本概念

1.1 什么是库?

库是一组预编译好的代码集合,供其他程序调用。在Linux下主要有两种类型:

类型文件名链接时机优点缺点
静态库.a (archive)编译时链接进可执行文件✅ 独立部署,无依赖
✅ 加载速度快
❌ 体积大
❌ 更新需重新编译
动态库.so (shared object)运行时动态加载✅ 体积小
✅ 可独立更新
✅ 内存共享
❌ 需要随程序分发
❌ 有依赖问题

1.2 适用场景

  • 静态库:嵌入式系统、需要单文件部署的场景

  • 动态库:大型项目、多程序共享、需频繁更新的场景


二、静态库制作指南

2.1 项目结构

text

my_lib/
├── include/
│   └── mylib.h           # 公开头文件
├── src/
│   ├── math_add.c        # 实现文件1
│   ├── math_sub.c        # 实现文件2
│   └── utils.c           # 实现文件3
└── build/                # 编译输出目录

2.2 头文件示例

c

// include/mylib.h
#ifndef MYLIB_H
#define MYLIB_H

// 数学运算
int add(int a, int b);
int sub(int a, int b);

// 工具函数
void print_hello(void);
#endif

2.3 源文件示例

c

// src/math_add.c
#include "mylib.h"

int add(int a, int b)
{
    return a + b;
}

c

// src/math_sub.c
#include "mylib.h"

int sub(int a, int b)
{
    return a - b;
}

c

// src/utils.c
#include "mylib.h"
#include <stdio.h>

void print_hello(void)
{
    printf("Hello from static library!\n");
}

2.4 手动编译命令

bash

# 1. 创建编译目录
mkdir -p build

# 2. 编译 .c 文件为 .o 目标文件
gcc -c -fPIC -Iinclude src/math_add.c -o build/math_add.o
gcc -c -fPIC -Iinclude src/math_sub.c -o build/math_sub.o
gcc -c -fPIC -Iinclude src/utils.c -o build/utils.o

# 3. 打包成静态库 .a
ar rcs build/libmylib.a build/math_add.o build/math_sub.o build/utils.o

# 4. 查看库内容
ar t build/libmylib.a
# 输出:
# math_add.o
# math_sub.o
# utils.o

# 5. 查看符号表
nm build/libmylib.a

2.5 ar命令详解

bash

ar rcs libxxx.a file1.o file2.o
参数含义说明
rreplace插入或替换目标文件
ccreate创建库(如果不存在)
ssymbol table创建索引表,加速链接

2.6 Makefile方式

makefile

# Makefile
CC = gcc
CFLAGS = -Wall -O2 -fPIC -Iinclude
TARGET = libmylib.a
SRCS = $(wildcard src/*.c)
OBJS = $(patsubst src/%.c, build/%.o, $(SRCS))

all: $(TARGET)

$(TARGET): $(OBJS)
	ar rcs $@ $^

build/%.o: src/%.c
	mkdir -p build
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -rf build $(TARGET)

install:
	sudo cp $(TARGET) /usr/local/lib/
	sudo cp include/mylib.h /usr/local/include/

.PHONY: all clean install

bash

# 使用
make          # 编译
make install  # 安装到系统
make clean    # 清理

2.7 CMake方式(推荐)

cmake

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mylib)

set(CMAKE_C_STANDARD 11)

include_directories(include)

set(SOURCES
    src/math_add.c
    src/math_sub.c
    src/utils.c
)

# 创建静态库
add_library(mylib STATIC ${SOURCES})

# 安装
install(TARGETS mylib
    ARCHIVE DESTINATION lib
)
install(FILES include/mylib.h
    DESTINATION include
)

bash

# 使用 CMake
mkdir build && cd build
cmake ..
make
sudo make install

三、动态库制作指南

3.1 手动编译命令

bash

# 1. 编译位置无关的目标文件(关键:-fPIC)
gcc -c -fPIC -Iinclude src/math_add.c -o build/math_add.o
gcc -c -fPIC -Iinclude src/math_sub.c -o build/math_sub.o
gcc -c -fPIC -Iinclude src/utils.c -o build/utils.o

# 2. 链接成动态库 .so
gcc -shared -Wl,-soname,libmylib.so.1 -o build/libmylib.so.1.0.0 build/*.o

# 3. 创建符号链接
ln -sf libmylib.so.1.0.0 build/libmylib.so.1
ln -sf libmylib.so.1.0.0 build/libmylib.so

# 4. 查看动态库信息
ldd build/libmylib.so.1.0.0
readelf -d build/libmylib.so.1.0.0

3.2 -fPIC 参数详解

-fPIC (Position Independent Code) 是动态库必需的参数:

  • 作用:生成位置无关代码,允许库在内存任意位置加载

  • 为什么需要:动态库需要在多个进程间共享,地址不能固定

  • 不使用的后果:链接时会报错,或运行时无法加载

3.3 Makefile方式

makefile

# Makefile
CC = gcc
CFLAGS = -Wall -O2 -fPIC -Iinclude
LDFLAGS = -shared
VERSION = 1.0.0
SONAME = libmylib.so.1
TARGET = libmylib.so.$(VERSION)

SRCS = $(wildcard src/*.c)
OBJS = $(patsubst src/%.c, build/%.o, $(SRCS))

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -Wl,-soname,$(SONAME) -o $@ $^
	ln -sf $@ build/$(SONAME)
	ln -sf $@ build/libmylib.so

build/%.o: src/%.c
	mkdir -p build
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -rf build libmylib.so*

install:
	sudo cp build/libmylib.so.$(VERSION) /usr/local/lib/
	sudo ln -sf /usr/local/lib/libmylib.so.$(VERSION) /usr/local/lib/$(SONAME)
	sudo ln -sf /usr/local/lib/libmylib.so.$(VERSION) /usr/local/lib/libmylib.so
	sudo cp include/mylib.h /usr/local/include/
	sudo ldconfig

.PHONY: all clean install

3.4 CMake方式(推荐)

cmake

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mylib VERSION 1.0.0)

set(CMAKE_C_STANDARD 11)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

include_directories(include)

set(SOURCES
    src/math_add.c
    src/math_sub.c
    src/utils.c
)

# 创建动态库
add_library(mylib SHARED ${SOURCES})

set_target_properties(mylib PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
)

# 安装
install(TARGETS mylib
    LIBRARY DESTINATION lib
)
install(FILES include/mylib.h
    DESTINATION include
)

bash

# 使用
mkdir build && cd build
cmake ..
make
sudo make install
sudo ldconfig   # 更新动态库缓存

四、库的使用方法

4.1 静态库的使用

c

// main.c
#include "mylib.h"
#include <stdio.h>

int main()
{
    printf("10 + 5 = %d\n", add(10, 5));
    printf("10 - 5 = %d\n", sub(10, 5));
    print_hello();
    return 0;
}

bash

# 方式1:直接指定 .a 文件
gcc main.c libmylib.a -o main

# 方式2:使用 -L 和 -l
gcc main.c -L. -lmylib -o main

# 方式3:安装到系统后
gcc main.c -lmylib -o main

# 运行
./main

4.2 动态库的使用

bash

# 编译时链接
gcc main.c -L. -lmylib -o main

# 运行时指定库路径
LD_LIBRARY_PATH=. ./main

# 或安装到系统路径后
sudo ldconfig
./main

4.3 运行时动态加载(高级用法)

c

// dl_main.c - 运行时动态加载库
#include <stdio.h>
#include <dlfcn.h>

// 函数指针类型定义
typedef int (*add_func)(int, int);
typedef void (*hello_func)(void);

int main()
{
    // 1. 动态加载库
    void* handle = dlopen("./libmylib.so", RTLD_LAZY);
    if (!handle) {
        printf("dlopen error: %s\n", dlerror());
        return -1;
    }
    
    // 2. 获取函数指针
    add_func add = (add_func)dlsym(handle, "add");
    hello_func print_hello = (hello_func)dlsym(handle, "print_hello");
    
    // 3. 调用函数
    printf("10 + 5 = %d\n", add(10, 5));
    print_hello();
    
    // 4. 卸载库
    dlclose(handle);
    return 0;
}

bash

# 编译时需要链接 dl 库
gcc dl_main.c -ldl -o main
./main

五、常用命令速查表

命令作用示例
gcc -c编译为目标文件gcc -c -fPIC file.c
ar rcs创建静态库ar rcs lib.a file1.o file2.o
ar t查看静态库内容ar t lib.a
ar x从静态库提取 .oar x lib.a
gcc -shared创建动态库gcc -shared -o lib.so file.o
nm查看目标文件符号nm lib.a
ldd查看动态库依赖ldd lib.so
readelf -d查看动态库信息readelf -d lib.so
ldconfig更新动态库缓存sudo ldconfig
objdump -d反汇编查看objdump -d lib.a

六、库的版本管理规范

6.1 命名规范

text

lib<name>.so.<major>.<minor>.<patch>

示例:libsecurity.so.1.2.3
         │   │ │ │
         │   │ │ └── patch (bug修复,兼容)
         │   │ └──── minor (新功能,兼容)
         │   └────── major (不兼容更新)
         └────────── 库名

6.2 符号链接体系

bash

# 实际文件(带完整版本号)
libsecurity.so.1.0.0

# 符号链接(运行时用,只带主版本号)
libsecurity.so.1 -> libsecurity.so.1.0.0

# 符号链接(编译时用,不带版本号)
libsecurity.so -> libsecurity.so.1

6.3 创建版本化库

bash

# 编译时指定 soname
gcc -shared -Wl,-soname,libsecurity.so.1 -o libsecurity.so.1.0.0 *.o

# 创建符号链接
ln -sf libsecurity.so.1.0.0 libsecurity.so.1
ln -sf libsecurity.so.1.0.0 libsecurity.so

# 安装
sudo cp libsecurity.so.1.0.0 /usr/local/lib/
sudo ln -sf /usr/local/lib/libsecurity.so.1.0.0 /usr/local/lib/libsecurity.so.1
sudo ln -sf /usr/local/lib/libsecurity.so.1.0.0 /usr/local/lib/libsecurity.so
sudo ldconfig

七、常见问题排查

7.1 找不到动态库

错误信息:

text

error while loading shared libraries: libmylib.so: cannot open shared object file: No such file or directory

解决方法:

bash

# 方法1:临时设置环境变量
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH
./main

# 方法2:更新系统库缓存
sudo cp libmylib.so /usr/local/lib/
sudo ldconfig

# 方法3:配置文件方式
sudo echo "/path/to/lib" > /etc/ld.so.conf.d/mylib.conf
sudo ldconfig

7.2 查看可执行文件依赖的库

bash

ldd ./main

# 输出示例:
# linux-vdso.so.1 (0x00007ffe5e7e4000)
# libmylib.so.1 => /usr/local/lib/libmylib.so.1 (0x00007f8a2c000000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a2be00000)

7.3 查看库中导出的函数

bash

# 查看动态库导出符号
nm -D libmylib.so

# 过滤只查看函数(T表示代码段)
nm -D libmylib.so | grep ' T '

# 输出示例:
# 0000000000001110 T add
# 0000000000001120 T print_hello
# 0000000000001130 T sub

7.4 静态库 vs 动态库体积对比

bash

# 静态库
ls -lh libmylib.a
# -rw-r--r-- 1 user user 15K libmylib.a

# 动态库
ls -lh libmylib.so
# -rwxr-xr-x 1 user user 12K libmylib.so

# 链接静态库的可执行文件
ls -lh main_static
# -rwxr-xr-x 1 user user 28K main_static

# 链接动态库的可执行文件
ls -lh main_shared
# -rwxr-xr-x 1 user user 17K main_shared

八、快速参考卡片

静态库制作三步走

bash

# 1. 编译 .c -> .o
gcc -c -Iinclude src/*.c

# 2. 打包 .o -> .a
ar rcs libmylib.a *.o

# 3. 使用
gcc main.c libmylib.a -o main

动态库制作三步走

bash

# 1. 编译 .c -> .o (位置无关)
gcc -c -fPIC -Iinclude src/*.c

# 2. 链接 .o -> .so
gcc -shared -o libmylib.so *.o

# 3. 使用
gcc main.c -L. -lmylib -o main
LD_LIBRARY_PATH=. ./main

九、最佳实践总结

场景推荐方案原因
嵌入式系统静态库部署简单,无依赖
大型应用动态库节省内存,便于更新
系统库动态库 + 版本控制多程序共享,兼容性管理
私有不公开代码静态库不需要处理符号冲突
插件系统动态库 + dlopen运行时加载,可热插拔
性能敏感场景静态库避免 PLT 跳转开销

十、结语

本文详细介绍了 Linux 下静态库和动态库的制作与使用方法。掌握这些技能后,你可以:

  • 将通用代码封装成库,供多个项目复用

  • 控制代码的可见性和依赖关系

  • 优化程序的部署和更新流程

建议先用手动命令跑通一遍流程,理解每个步骤的作用,再使用 Makefile 或 CMake 进行自动化构建。


📢 如果这篇文章对你有帮助,欢迎点赞、收藏、转发!

有问题欢迎在评论区留言讨论。

更多推荐