🎬 个人主页:艾莉丝努力练剑
❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 1 ~> 库的分类与基本原理
-
- 1.1 静态库与动态库的定义
- 1.2 系统中的库示例
- 2 ~> 静态库的制作与使用
-
- 2.1 静态库制作流程
- 2.2 静态库的使用方式
- 3 ~> 动态库的制作与使用
-
- 3.1 动态库制作要点
- 3.2 动态库运行时搜索路径
- 4 ~> ELF 文件格式深度解析
-
- 4.1 ELF 文件类型与结构
- 4.2 ELF 文件的四部分组成
- 4.3 最常见的节区(Sections)
- 5 ~> 静态链接机制深度剖析
-
- 5.1 静态链接的本质
- 5.2 查看目标文件符号表
- 6 ~> 动态链接与程序加载过程
-
- 6.1 程序启动流程
- 6.2 动态库的共享机制
- 6.3 GOT/PLT 延迟绑定机制
- 7 ~> 动静态链接对比总结
-
- 7.1 特性对比表格
- 7.2 应用场景选择
- 结尾

1 ~> 库的分类与基本原理
1.1 静态库与动态库的定义
静态库(.a文件)是指程序在编译链接时将库的代码完整复制到可执行文件中,程序运行时不再需要外部库文件。静态库在链接阶段被直接嵌入到最终的可执行文件中,形成独立的二进制程序。
动态库(.so文件)则采用不同的机制:程序运行时才去链接动态库代码。与动态库链接的可执行文件仅包含所用函数的入口地址表,而非整个目标文件的机器码。在程序执行前,操作系统将动态库从磁盘加载到内存,这个过程称为动态链接。
1.2 系统中的库示例
在典型的 Linux 系统中,可以观察到标准库的实现:
# Ubuntu系统标准库示例
$ ls -l /lib/x86_64-linux-gnu/libc-2.31.so
-rwxr-xr-x 1 root root 2029592 May 1 02:20 /lib/x86_64-linux-gnu/libc-2.31.so
$ ls -l /lib/x86_64-linux-gnu/libc.a
-rw-r–r– 1 root root 5747594 May 1 02:20 /lib/x86_64-linux-gnu/libc.a
从文件大小可以看出,静态库通常比动态库大,因为静态库包含了所有函数的完整实现代码。
2 ~> 静态库的制作与使用
2.1 静态库制作流程
静态库的制作基于目标文件(.o文件)的归档过程。以下是完整的制作示例:
源代码文件结构:
// my_stdio.h – 头文件定义
#pragma once
#define SIZE 1024
struct IO_FILE {
int flag;
int fileno;
char outbuffer[SIZE];
int cap;
int size;
};
typedef struct IO_FILE mFILE;
mFILE *mfopen(const char *filename, const char *mode);
int mfwrite(const void *ptr, int num, mFILE *stream);
void mfflush(mFILE *stream);
void mfclose(mFILE *stream);
// my_stdio.c – 实现文件
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
mFILE *mfopen(const char *filename, const char *mode) {
int fd = –1;
if(strcmp(mode, "r") == 0) {
fd = open(filename, O_RDONLY);
} else if(strcmp(mode, "w") == 0) {
fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
}
// 其余实现代码…
return mf;
}
Makefile 配置:
libmystdio.a: my_stdio.o my_string.o
@ar -rc $@ $^
@echo "build $^ to $@ … done"
%.o: %.c
@gcc -c $<
@echo "compling $< to $@ … done"
.PHONY: clean
clean:
@rm -rf *.a *.o stdc*
使用 ar 工具创建静态库:
$ make libmystdio.a
$ ar -tv libmystdio.a
rw-rw-r– 1000/1000 2848 Oct 29 14:35 2024 my_stdio.o
rw-rw-r– 1000/1000 1272 Oct 29 14:35 2024 my_string.o
2.2 静态库的使用方式
编译选项详解:
# 场景1:库文件在系统路径
$ gcc main.c -lmystdio
# 场景2:库文件在当前目录
$ gcc main.c -L. -lmystdio
# 场景3:自定义头文件和库路径
$ gcc main.c -I./include -L./lib -lmystdio
关键编译选项说明:
- -L:指定库文件搜索路径
- -I:指定头文件搜索路径
- -l:指定要链接的库名(去掉 lib 前缀和.a 后缀)
3 ~> 动态库的制作与使用
3.1 动态库制作要点
动态库制作需要位置无关代码(PIC)支持:
libmystdio.so: my_stdio.o my_string.o
gcc -o $@ $^ -shared
%.o: %.c
gcc -fPIC -c $<
关键编译选项:
- -fPIC:生成位置无关代码,这是动态库的必要条件
- -shared:指示生成共享库(动态库)
3.2 动态库运行时搜索路径
动态库在运行时需要被系统找到,否则会出现加载错误:
# 查看程序依赖的动态库
$ ldd myprogram
linux-vdso.so.1 (0x00007ffeeff3fe000)
libmystdio.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a1b200000)
解决方案:
4 ~> ELF 文件格式深度解析
4.1 ELF 文件类型与结构
ELF(Executable and Linkable Format)是 Linux 下的标准二进制文件格式,主要分为四种类型:
| REL | 可重定位文件 | .o 目标文件 |
| EXEC | 可执行文件 | 编译后的程序 |
| DYN | 共享目标文件 | .so 动态库 |
| CORE | 核心转储文件 | core dump 文件 |
4.2 ELF 文件的四部分组成
ELF 文件由四个核心部分组成:
4.3 最常见的节区(Sections)
# 查看可执行文件的节区
$ readelf -S hello
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[13] .text PROGBITS 00000000004004c0 000004c0
000000000000016d 0000000000000000 AX 0 0 16
[24] .data PROGBITS 0000000000601000 00002000
0000000000000010 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000601010 00002010
0000000000000024 0000000000000000 WA 0 0 1
[12] .dynsym DYNSYM 0000000000400390 00000390
00000000000000a0 0000000000000018 A 5 1 8
关键节区说明:
- .text:存放程序代码
- .data:已初始化的全局变量
- .bss:未初始化的全局变量
- .plt:过程链接表,用于动态链接
- .got:全局偏移表,存储外部符号地址
5 ~> 静态链接机制深度剖析
5.1 静态链接的本质
静态链接是将多个目标文件(.o)合并成一个可执行文件的过程。链接器主要完成以下工作:
5.2 查看目标文件符号表
# 查看目标文件的符号表
$ nm my_stdio.o
0000000000000000 T mfclose
0000000000000146 T mfflush
0000000000000000 T mfopen
00000000000001a8 T mfwrite
U write
# 查看重定位信息
$ readelf -r my_stdio.o
Relocation section '.rela.text' at offset 0x648 contains 6 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000002a 001500000002 R_X86_64_PC32 0000000000000000 write – 4
6 ~> 动态链接与程序加载过程
6.1 程序启动流程
动态链接程序的启动涉及复杂的协作过程:
6.2 动态库的共享机制
动态库的核心优势在于进程间共享:
// 进程A和进程B共享同一个libc.so的物理内存页面
进程A虚拟地址空间 → [libc代码] ← 进程B虚拟地址空间
0x7fxxxxxx (libc) [内存页] 0x7fxxxxxx (libc)
这种共享机制通过虚拟内存和写时复制(Copy-on-Write)技术实现,显著减少了内存占用。
6.3 GOT/PLT 延迟绑定机制
动态链接采用延迟绑定(Lazy Binding)优化性能:
- GOT(Global Offset Table):存储外部函数和变量的实际地址
- PLT(Procedure Linkage Table):包含跳转到 GOT 的桩代码
# PLT条目示例
push %GOT[1] # 第一次跳转到动态链接器
jmp *%GOT[2] # 调用动态链接器
第一次调用函数时,流程为:PLT → 动态链接器(解析真实地址)→ 更新 GOT → 直接跳转到目标函数。后续调用直接通过 GOT 跳转,避免重复解析。
7 ~> 动静态链接对比总结
7.1 特性对比表格
| 文件大小 | 较大(包含全部库代码) | 较小(仅包含引用信息) |
| 内存占用 | 每个进程独立副本 | 多进程共享库代码 |
| 部署便利性 | 简单(单文件部署) | 需要确保库文件存在 |
| 更新维护 | 需要重新编译整个程序 | 只需替换库文件 |
| 加载速度 | 较快(无运行时链接) | 首次加载稍慢 |
| 兼容性 | 库版本固定 | 可能存在版本冲突 |
7.2 应用场景选择
选择静态链接的情况:
- 需要单文件部署的应用程序
- 对性能要求极高的场景
- 嵌入式系统等资源受限或环境不可控
选择动态链接的情况:
- 需要多个程序共享库代码
- 频繁更新功能的场景
- 磁盘和内存资源需要优化
- 大型系统应用程序
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|
艾莉丝努力练剑
C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 👀 ❤️ ⭐ 💬 不要忘记给博主“一键四连”哦! “今日练剑达成!” “技术之路难免有困惑,但同行的人会让前进更有方向。” |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
往期回顾:
【Linux:文件】库的制作与原理进阶
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა





