当前位置: 首页 > news >正文

【Linux:文件】Linux 动静态库详解::制作、使用、原理与实战

🔥小叶-duck:个人主页

❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一. 库的基础认知:是什么?有哪些?

1.1 库的本质:是什么?

1.2 库的分类与系统位置:有哪些

1.3 编译器链接行为

1.4 预备工作:自定义库源码

二、静态库(编译时链接,独立运行)

2.1 整体图示

2.2 静态库制作流程(Makefile自动化 ,更简便)

2.2.1 编写 Makefile

2.2.2 查看静态库内容

2.3 静态库使用场景与命令

2.4 静态库核心特点

三. 动态库(运行时链接,共享复用)

3.1 动态库制作流程(Makefile自动化)

3.1.1 编写 Makefile

3.2 动态库使用:编译与运行时依赖

3.2.1 问题现象

3.2.2 解决方案

3.3 动态库核心特点

四. 动静态库对比与选型建议

五、实战:使用外部库(ncurses 图形库)

5.1 安装 ncurses 库

5.2. 测试代码(大家可以自己试试别的)

结束语


前言

在 Linux 开发中,库是实现代码复用的核心方式。无论是 C 标准库提供的 printf,还是我们自行封装的工具函数,都能编译为库文件,供多个项目直接调用。库主要分为静态库(.a)动态库(.so),二者在编译链接、内存占用、部署更新等方面存在显著差异。熟练掌握库的制作、使用及底层原理,是 Linux 开发的必备能力。

一. 库的基础认知:是什么?有哪些?

1.1 库的本质:是什么?

库是编译后的二进制文件,包含可复用的代码和数据,本质是 “提前写好、经过验证的成熟代码”。其核心价值在于:

  • 避免重复开发:无需从零实现基础功能(如字符串处理、文件 IO);
  • 简化项目管理:将复杂功能拆分到库中,降低主项目复杂度;
  • 隐藏实现细节:只暴露接口,保护核心逻辑。

1.2 库的分类与系统位置:有哪些

Linux 下库分为两类,命名和存储路径有明确规范:

类型后缀 (Linux)后缀 (Windows)系统默认路径核心特征
静态库.a.lib/lib,/usr/lib,/usr/local/lib编译时链接,可执行程序独立运行
动态库.so.dll/lib64,/usr/lib64,/usr/local/lib64运行时链接,多程序共享

系统中的库示例:

Ubuntu 系统

C 标准库:

$ 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

C++ 标准库:

$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -l # 动态库 lrwxrwxrwx 1 root root 40 Oct 24 2022 /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6 $ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a # 静态库 /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a

CentOS 系统

C 标准库:

$ ls /lib64/libc-2.17.so -l # 动态库 -rwxr-xr-x 1 root root 2156592 Jun 4 23:05 /lib64/libc-2.17.so $ ls /lib64/libc.a -l # 静态库 -rw-r--r-- 1 root root 5105516 Jun 4 23:05 /lib64/libc.a

C++ 标准库:

$ ls /lib64/libstdc++.so.6 -l # 动态库(软链接到实际版本) lrwxrwxrwx 1 root root 19 Sep 18 20:59 /lib64/libstdc++.so.6 -> libstdc++.so.6.0.19 $ ls /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a -l # 静态库 -rw-r--r-- 1 root root 2932366 Sep 30 2020 /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a

1.3 编译器链接行为

一个可执行程序可能用到许多库,这些库运行有的是静态库,有的是动态库。编译器默认使用动态链接库,只有在该目录下找不到动态库.so的时候才会采用同名静态库。我们也可以使用 gcc 的-static选项强制设置链接静态库。

1.4 预备工作:自定义库源码

后续动静态库制作将基于以下自定义源码(模拟文件 IO 和字符串工具库):

(1)文件 IO 库(my_stdio.h/my_stdio.c)

// my_stdio.h #pragma once #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #define MAX 1024 #define NONE_FLUSH (1<<0)//不刷新 #define LINE_FLUSH (1<<1)//行刷新 #define FULL_FLUSH (1<<2)//全刷新 typedef struct IO_FILE { int fileno; //文件描述符 int flag; //文件标志位 char outbuffer[MAX]; //缓冲区 int bufferlen; //当前缓冲区数据长度 int flush_method; //刷新方式(宏) int _capacity; //缓冲区容量 }MYFILE; MYFILE *MyFopen(const char *path, const char *mode); int MyFwrite(const char *str, size_t size, size_t num, MYFILE* fp); void MyFclose(MYFILE *file); void MyFFlush(MYFILE *file);
// my_stdio.c #include "mystdio.h" MYFILE *CreatFile(int fd, int flag) { MYFILE *newfile = (MYFILE*)malloc(sizeof(MYFILE)); newfile->bufferlen = 0; newfile->fileno = fd; newfile->flag = flag; newfile->flush_method = LINE_FLUSH; newfile->_capacity = MAX; memset(newfile->outbuffer, 0, sizeof(newfile->outbuffer)); return newfile; } MYFILE *MyFopen(const char *path, const char *mode) { int fd = -1; int flag = 0; if(strcmp(mode, "w") == 0) { //写的方式 flag = O_CREAT | O_WRONLY | O_TRUNC; fd = open(path, flag, 0666); } else if(strcmp(mode, "r") == 0) { //读的方式 flag = O_RDONLY; fd = open(path, flag); } else if(strcmp(mode, "a") == 0) { //追加的方式 flag = O_CREAT | O_WRONLY | O_APPEND; fd = open(path, flag, 0666); } if(fd < 0) return NULL; return CreatFile(fd, flag); } int MyFwrite(const char *str, size_t size, size_t num, MYFILE* file) { //1、拷贝(先判断写入的数据是否会使缓冲区满,如果满了则先进行刷新再写入缓冲区) int sizeNum = size * num; if(file->bufferlen + sizeNum >= file->_capacity) { MyFFlush(file); } memcpy(file->outbuffer + file->bufferlen, str, sizeNum); file->bufferlen += sizeNum; //2、尝试判断是否满足刷新条件 if(file->flush_method == LINE_FLUSH && file->outbuffer[file->bufferlen - 1] == '\n') { MyFFlush(file); } return 0; } void MyFclose(MYFILE *file) { if(file->fileno < 0) return; MyFFlush(file); //系统调用close close(file->fileno); free(file); file = NULL; } void MyFFlush(MYFILE *file) { if(file->bufferlen <= 0) return; //当前缓冲区无数据则无需刷新 //系统调用write直接刷新 // 所谓的刷新就是把数据从用户缓冲区拷贝到内核 // 从用户缓冲区拷贝到内核这种模式叫做WB模式 // WB: Write Back(写回) int n = write(file->fileno, file->outbuffer, file->bufferlen); (void)n; // 刷新到外设,不仅仅要写入到内核缓冲区,还必须写到对应的硬件上 // WT模式,Write Though fsync(file->fileno); //缓冲区刷新完成后缓冲区就没有数据了 file->bufferlen = 0; }

(2)字符串库(my_string.h/my_string.c)

// my_string.h #pragma once int my_strlen(const char* s);
// my_string.c #include "my_string.h" int my_strlen(const char* s) { const char* end = s; while (*end != '\0') end++; return end - s; }
s; }
  • 总结与引入

二、静态库(编译时链接,独立运行)

静态库(.a)的核心特征是 “编译链接时,将库代码完整拷贝到可执行程序中”,生成的可执行程序不依赖外部库,可独立运行。

2.1 整体图示

  • 我们可以先看看这个图示的流程再来往下详细学习

2.2 静态库制作流程(Makefile自动化 ,更简便)

静态库通过ar(GNU 归档工具)制作,核心步骤:编译源码生成.o 文件 → 归档.o 文件为.a 静态库。

2.2.1编写 Makefile

target=libmyc.a src=$(wildcard *.c) obj=$(src:.c=.o) $(target):$(obj) @ar -rc $@ $^ @echo "build $^ to $@ ... done" %.o:%.c @gcc -c $< @echo "compling $< to $@ ... done" .PHONY:output output: @mkdir -p lib/include @mkdir -p lib/mylib @cp -f *.h lib/include @cp -f *.a lib/mylib @tar -czf lib.tgz lib @echo "output lib ... done" .PHONY:clean clean: @rm -rf lib lib.tgz $(target) *.o @echo "clean ... done"

ar 是 GNU 归档工具,rc表示replace and create

  • 后续操作如下图所示:

2.2.2 查看静态库内容

$ 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
  • t:列出静态库中的文件
  • v:显示详细信息

2.3 静态库使用场景与命令

静态库使用需指定 “头文件路径、库文件路径、库名”,核心命令格式(上面的使用过程中也体现了):

gcc 源文件.c -I头文件路径 -L库文件路径 -l库名 [-static]
  • -I:指定头文件搜索路径(默认搜索/usr/include等系统目录);
  • -L:指定库文件搜索路径(默认搜索/lib等系统目录);
  • -l:指定库名(需去掉前缀lib和后缀.a,如libmyc.a → -l myc);
  • -static:强制链接静态库(优先使用静态库,无静态库则报错)。

场景 1:头文件 / 库文件与源文件同目录

# 编译(同目录下可省略-I) gcc main.c -lmystdio -L . -static

场景 2:头文件 / 库文件在独立路径

# 假设库文件在 ./stdc/lib,头文件在 ./stdc/include gcc main.c -I./stdc/include -L./stdc/lib -lmystdio -static

场景 3:安装到系统目录(全局可用)

# 拷贝头文件到系统目录 sudo cp *.h /usr/include/ # 拷贝静态库到系统目录 sudo cp libmystdio.a /usr/lib/ # 直接编译(无需指定-I和-L,但是 -l 一定还是必须的) gcc main.c -lmystdio -static

2.4 静态库核心特点

  • 优点:可执行程序独立运行,不依赖外部库;运行时无需加载库,启动速度快;
    测试目标文件生成后,静态库删掉,程序照样可以运行。这是因为静态库的代码已经被链接到可执行文件中。
  • 缺点:可执行程序体积大(包含库代码);库更新后需重新编译链接;多个程序使用会重复占用磁盘和内存。

三. 动态库(运行时链接,共享复用)

动态库(.so)是指程序在运行的时候才去链接动态库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

3.1 动态库制作流程(Makefile自动化)

3.1.1 编写 Makefile

target=libmyc.so src=$(wildcard *.c) obj=$(src:.c=.o) $(target):$(obj) @gcc -shared -o $@ $^ @echo "build $^ to $@ ... done" # 编译PIC目标文件(位置无关码,支持任意地址加载) %.o:%.c @gcc -fPIC -c $< @echo "compling $< to $@ ... done" .PHONY:output output: @mkdir -p lib/include @mkdir -p lib/mylib @cp -f *.h lib/include @cp -f *.so lib/mylib @tar -czf lib.tgz lib @echo "output lib ... done" .PHONY:clean clean: @rm -rf lib lib.tgz $(target) *.o @echo "clean ... done"

关键编译选项:

  • -shared:表示生成共享库格式
  • -fPIC:产生位置无关码(position independent code)

后续操作如下图所示:

3.2 动态库使用:编译与运行时依赖

动态库编译命令与静态库类似,但运行时需确保系统能找到动态库(否则报错 “libmystdio.so not found”)。

  • 步骤 1:编译(同静态库命令,无需 - static)
# 场景1:同目录编译 gcc main.c -L. -lmystdio # 场景2:独立路径编译 gcc main.c -I./stdc/include -L./stdc/lib -lmystdio # 场景3:头文件和库文件安装到系统路径下编译 gcc main.c -lmystdio
  • 步骤 2:解决运行时库搜索路径

3.2.1 问题现象

$ ldd a.out linux-vdso.so.1 => (0x00007fff4d396000) libmystdio.so => not found libc.so.6 => /lib64/libc.so.6 (0x00007fa2aef30000) /lib64/ld-linux-x86-64.so.2 (0x00007fa2af2fe000)

3.2.2 解决方案

方法1:拷贝.so文件到系统共享库路径下

一般指/usr/lib、/usr/local/lib、/lib64或者其他库路径等。

方法2:向系统共享库路径下建立同名软连接

方法3:更改环境变量LD_LIBRARY_PATH

但是我们在前面学习环境变量的时候就讲解了:export不管是修改环境变量还是新增环境变量都只是在当前bash进程中进行操作,也就是说 export 修改变量 → 只改这个进程内部的环境变量表,当我们关闭当前的xshell后,这个bash进程也就结束了。
那么当我们再打开新的xshell时就会创建新的bash进程,也就会生成新的环境变量表,则我们修改的操作也就没了。

那怎么让我们修改的环境变量真正永久保存呢?就需要写到配置文件当中。

方法4:ldconfig 方案

  1. 配置/etc/ld.so.conf.d/
  2. ldconfig更新

小结:

3.3 动态库核心特点

  • 优点:可执行程序体积小;库更新后无需重新编译(替换.so 文件即可);多个程序共享库代码,节省资源;
  • 缺点:运行时依赖动态库,缺失会导致程序无法启动;启动时需加载库,速度略慢于静态库。

四. 动静态库对比与选型建议

对比维度静态库 (.a)动态库 (.so)
链接时机编译、链接阶段程序运行阶段
可执行程序体积大(库代码被复制进去)小(仅记录依赖信息)
运行依赖无需外部文件,独立运行必须依赖对应的.so 文件
库更新需重新编译、链接整个程序直接替换.so 文件即可生效
内存占用多个程序重复占用内存多个程序共享内存中的同一份代码
编译速度快(链接后无需额外处理)略慢(需处理动态链接信息)
适用场景小程序、嵌入式(追求无依赖)大型项目、多程序共享(节省资源)

选型建议:

  • 若程序需独立部署(如嵌入式设备),选静态库;
  • 若追求启动速度和稳定性,选静态库。
  • 若多个程序共用同一功能(如公司内部工具库),选动态库;
  • 若库更新频繁(如业务逻辑迭代快),选动态库;

相关问题:

五、实战:使用外部库(ncurses 图形库)

除了自定义库,Linux 系统提供大量现成外部库,以ncurses(终端图形库)为例,演示外部库的安装与使用。

5.1 安装 ncurses 库

# CentOS sudo yum install -y ncurses-devel # Ubuntu sudo apt install -y libncurses-dev

5.2. 测试代码(大家可以自己试试别的)

#include <ncurses.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <locale.h> // 开启UTF-8 #define SPEED 120000 #define MAX_LEN 100 typedef struct { int x; int y; } Node; Node snake[MAX_LEN]; int len = 3; int dir = KEY_RIGHT; int foodX, foodY; int score = 0; void createFood() { foodX = rand() % (COLS - 2) + 1; foodY = rand() % (LINES - 4) + 2; } void initSnake() { len = 3; dir = KEY_RIGHT; score = 0; snake[0].x = COLS / 2; snake[0].y = LINES / 2; snake[1].x = COLS / 2 - 1; snake[1].y = LINES / 2; snake[2].x = COLS / 2 - 2; snake[2].y = LINES / 2; createFood(); } void draw() { clear(); mvprintw(0, 0, "Snake Game | Score: %d | Arrow keys to move | q:Quit", score); mvaddch(foodY, foodX, '*'); for (int i = 0; i < len; i++) { if (i == 0) mvaddch(snake[i].y, snake[i].x, 'O'); // 蛇头换成O else mvaddch(snake[i].y, snake[i].x, '#'); // 蛇身# } refresh(); } void move_snake() { for (int i = len - 1; i > 0; i--) { snake[i] = snake[i - 1]; } switch (dir) { case KEY_UP: snake[0].y--; break; case KEY_DOWN: snake[0].y++; break; case KEY_LEFT: snake[0].x--; break; case KEY_RIGHT: snake[0].x++; break; } } int checkHit() { if (snake[0].x < 0 || snake[0].x >= COLS || snake[0].y < 2 || snake[0].y >= LINES - 1) return 1; for (int i = 1; i < len; i++) { if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) return 1; } return 0; } void eatFood() { if (snake[0].x == foodX && snake[0].y == foodY) { score += 10; len++; createFood(); } } int main() { setlocale(LC_ALL, "en_US.UTF-8"); // 强制UTF-8 initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0); nodelay(stdscr, TRUE); srand((unsigned)time(NULL)); initSnake(); while (1) { draw(); usleep(SPEED); int key = getch(); if (key != ERR) { if (key == KEY_UP && dir != KEY_DOWN) dir = KEY_UP; else if (key == KEY_DOWN && dir != KEY_UP) dir = KEY_DOWN; else if (key == KEY_LEFT && dir != KEY_RIGHT) dir = KEY_LEFT; else if (key == KEY_RIGHT && dir != KEY_LEFT) dir = KEY_RIGHT; else if (key == 'q') break; } move_snake(); if (checkHit()) break; eatFood(); } mvprintw(LINES / 2, COLS / 2 - 6, "Game Over!"); refresh(); sleep(1); endwin(); return 0; }
# 编译(-lncurses指定链接ncurses库) gcc test.c -o test -std=c99 -lncurses # 运行 ./test

结束语

动静态库是 Linux 开发中实现代码复用的核心机制。熟练掌握其制作、使用方法与选型策略,能够有效提升开发效率、优化项目结构。静态库适用于追求程序独立、简化部署的场景;动态库则更适合多进程共享、节省资源、便于库文件独立更新的场景。
在实际开发中,可根据业务需求灵活选择。本文从基础概念到实战应用,完整覆盖了库开发的核心流程。后续可进一步深入学习库的版本管理、符号隐藏、动态加载(dlopen/dlsym)等高级特性,从而构建更健壮、更灵活的程序体系。

http://www.jsqmd.com/news/881051/

相关文章:

  • Codex入门19-数据库操作(解放双手:用自然语言写SQL、建表和数据迁移)
  • Deep Clustering of Tabular Data by Weighted Gaussian Distribution Learning——基于加权高斯分布学习的表格数据深度聚类
  • qemu和gcc编译
  • 从单用户到团队协作:给你的Ubuntu服务器配置多用户SSH访问权限(附sudo权限管理)
  • AI agent案例汇总:基于 LangGraph 的智能对话 Agent 实现
  • 文章三:Elasticsearch 集群恢复和索引分布
  • 2026年当前,上海别墅大宅新风系统可靠服务商深度解析 - 2026年企业推荐榜
  • 机器学习数据集详解,公开免费数据集获取渠道汇总
  • Try和expect的正确使用方式
  • 连锁董事网络指标数据(2001-2024)
  • 2026电工杯数学建模竞赛A题论文、代码、数据
  • 数据结构:线性表和顺序表
  • 2026槽式电缆桥架优质推荐指南:网格电缆桥架、铝合金走线架、不锈钢电缆桥架、北京电缆桥架厂家、托盘式电缆桥架选择指南 - 优质品牌商家
  • Claude Code 在安装vscode插件时遇到的问题。
  • 告别图形界面!5个CUPS命令行技巧,让你在Linux终端高效管理打印机
  • 2026微型舵机优质推荐榜:小型舵机/尾翼用方扁舵机/工业舵机/德晟舵机/数字舵机/无人机舵机/无刷舵机/最小的舵机/选择指南 - 优质品牌商家
  • 2026电工杯数学建模竞赛A题论文、代码、数据(改进)
  • # 网页设计学习感悟
  • 朝晖玻璃钢:玻璃钢保温水箱/玻璃钢消防水箱/玻璃钢罐化粪池/碳钢水箱/立式不锈钢水箱/组合式玻璃钢水箱/雨水一体化提升泵站/选择指南 - 优质品牌商家
  • 别再手动下载DLL了!用Windows自带工具SFC/SCANNOW一键修复kernel32.dll错误
  • 别再让系统‘无家可归’:给已用满空间的Win10 SSD无损创建EFI引导分区指南
  • 2026年紫外线杀菌除藻灯优质厂家深度解析:聚焦技术、产能与服务三角 - 2026年企业推荐榜
  • Titanic数据集分析避坑指南:新手常犯的3个错误及如何修正
  • ubuntu2026.04部署k8s1.36版本的傻瓜式教程(注:运行时为docker,网络插件为calico)
  • 一文讲清楚规则、Skill、MCP
  • 2026泛塞封密封圈优质品牌推荐:聚四氟乙烯密封圈/铁氟龙密封圈/高分子材料密封圈/O型圈/PEEK密封圈/PU密封圈/选择指南 - 优质品牌商家
  • 别再让Ubuntu卡成PPT!手把手教你用swapfile把交换空间从1G扩容到64G(附权限修复)
  • 【iOS】底层原理:理解dyld
  • 告别虚拟机!手把手教你用U盘给新电脑装Win11+统信UOS 1060双系统(保姆级分区教程)
  • Win10开机WiFi列表全空?先别慌,按这个‘服务状态排查流程图’走一遍