小叶-duck个人主页❄️个人专栏《Data-Structure-Learning》《C入门到进阶自我学习过程记录》《Linux操作系统从入门到实践》《Qt从入门到实践》《算法题讲解指南》--优选算法《算法题讲解指南》--递归、搜索与回溯算法《算法题讲解指南》--动态规划算法✨未择之路不须回头已择之路纵是荆棘遍野亦作花海遨游目录前言一. 库的基础认知是什么有哪些1.1 库的本质是什么1.2 库的分类与系统位置有哪些1.3 编译器链接行为1.4 预备工作自定义库源码二、静态库(编译时链接独立运行)2.1 整体图示2.2 静态库制作流程Makefile自动化 更简便2.2.1 编写 Makefile2.2.2 查看静态库内容2.3 静态库使用场景与命令2.4 静态库核心特点三. 动态库(运行时链接共享复用)3.1 动态库制作流程Makefile自动化3.1.1 编写 Makefile3.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.aC 标准库$ 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.aCentOS 系统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.aC 标准库$ 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.a1.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 (10)//不刷新 #define LINE_FLUSH (11)//行刷新 #define FULL_FLUSH (12)//全刷新 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自动化 更简便静态库通过arGNU 归档工具制作核心步骤编译源码生成.o 文件 → 归档.o 文件为.a 静态库。2.2.1编写 Makefiletargetlibmyc.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 ... donear 是 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.ot列出静态库中的文件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 -static2.4 静态库核心特点优点可执行程序独立运行不依赖外部库运行时无需加载库启动速度快测试目标文件生成后静态库删掉程序照样可以运行。这是因为静态库的代码已经被链接到可执行文件中。缺点可执行程序体积大包含库代码库更新后需重新编译链接多个程序使用会重复占用磁盘和内存。三. 动态库(运行时链接共享复用)动态库.so是指程序在运行的时候才去链接动态库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表而不是外部函数所在目标文件的整个机器码。在可执行文件开始运行以前外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中这个过程称为动态链接。动态库可以在多个程序间共享所以动态链接使得可执行文件更小节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用节省了内存和磁盘空间。3.1 动态库制作流程Makefile自动化3.1.1 编写 Makefiletargetlibmyc.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进程也就会生成新的环境变量表则我们修改的操作也就没了。那怎么让我们修改的环境变量真正永久保存呢就需要写到配置文件当中。方法4ldconfig 方案配置/etc/ld.so.conf.d/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-dev5.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 -stdc99 -lncurses # 运行 ./test结束语动静态库是 Linux 开发中实现代码复用的核心机制。熟练掌握其制作、使用方法与选型策略能够有效提升开发效率、优化项目结构。静态库适用于追求程序独立、简化部署的场景动态库则更适合多进程共享、节省资源、便于库文件独立更新的场景。在实际开发中可根据业务需求灵活选择。本文从基础概念到实战应用完整覆盖了库开发的核心流程。后续可进一步深入学习库的版本管理、符号隐藏、动态加载dlopen/dlsym等高级特性从而构建更健壮、更灵活的程序体系。