【Linux:动静态库】Linux 动静态库与可执行文件
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
🎬 艾莉丝的简介:

文章目录

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)
解决方案:
- 将库文件复制到系统库目录(如
/usr/lib) - 设置
LD_LIBRARY_PATH环境变量 - 修改
/etc/ld.so.conf配置文件 - 使用
rpath编译选项
4 ~> ELF 文件格式深度解析
4.1 ELF 文件类型与结构
ELF(Executable and Linkable Format)是 Linux 下的标准二进制文件格式,主要分为四种类型:
| ELF 类型 | 描述 | 典型文件 |
|---|---|---|
| REL | 可重定位文件 | .o 目标文件 |
| EXEC | 可执行文件 | 编译后的程序 |
| DYN | 共享目标文件 | .so 动态库 |
| CORE | 核心转储文件 | core dump 文件 |
4.2 ELF 文件的四部分组成
ELF 文件由四个核心部分组成:
- ELF Header:描述文件的基本属性和组织结构
- Program Header Table:执行视图,用于程序加载
- Section Header Table:链接视图,用于编译链接
- Sections:实际的代码、数据等内容
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 程序启动流程
动态链接程序的启动涉及复杂的协作过程:
- 内核加载:操作系统将可执行文件映射到进程地址空间
- 解释器介入:通过
PT_INTERP段指定的动态链接器(如/lib64/ld-linux-x86-64.so.2)开始工作 - 符号解析:动态链接器解析所有未定义的符号
- 重定位完成:更新 GOT/PLT 表中的地址
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们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)