ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux中动静态库的理解

Linux中动静态库的理解 软硬链接硬链接ln a b,就是将目标文件a硬链接到文件bls -i 查看文件的inodels -li查看所有文件的inode原理Linux 文件由inode 数据块组成inode记录文件元信息权限、大小、指向数据块指针文件名只是 inode 的一个别名。硬链接本质多个文件名指向同一个 inode特点同一个 inode 号多个文件名共享磁盘数据只能对文件创建不能给目录创建硬链接系统目录本身有. .. 硬链接除外不能跨文件系统分区inode 只在当前文件系统有效删除一个硬链接只是删掉一个文件名只要还有一个链接存在文件数据不会删除修改任意一个硬链接文件所有链接文件都会同步变化共享同一份数据不额外占用磁盘空间仅增加目录项记录创建并写入内容到test.txt观察inode软连接ln -s a b将文件a软链接到文件btest.txt 和 hard.txt inode 相同soft.txt 是独立 inode原理软链接本身是独立的小文件它的 inode 指向的数据内容保存的是源文件路径。类似 Windows 的快捷方式。特点软链接有自己独立的 inode是一个单独文件可以链接文件也可以链接目录支持跨分区、跨文件系统如果源文件删除软链接依然存在但变成失效链接红闪烁访问会提示 No such file修改软链接对应的文件实际修改的是源文件数据软链接文件本身占用少量磁盘空间存放路径字符串相对路径创建软链接时源路径是相对于软链接所在目录不是当前执行命令目录很容易踩坑使用场景✅硬链接重要文件多备份防止误删同一分区内✅软链接版本切换python 多版本、目录迁移、快捷访问、跨分区链接特性硬链接软链接inode和源文件相同独立 inode本质文件名别名保存路径的独立文件支持目录❌ 不支持✅ 支持跨分区❌ 不支持✅ 支持删除源文件文件还在链接失效文件大小和源一致路径字符串长度权限和源一致软链接权限几乎无效以源文件为准动静态库动态库库库是写好的现有的成熟的可以直接复用的代码实现不需要去写代码时需要应用到然后在去重写代码直接调用库中的代码应用就可以。动态库.so原理链接阶段只记录库的符号信息不拷贝代码程序运行时才加载动态库到内存。多个进程可以共享内存里同一份库代码。特点✅ 优点可执行文件小内存中只加载一份库节省内存库升级替换.so文件程序不用重新编译接口不变即可❌ 缺点运行环境必须存在对应.so否则程序无法启动经典error while loading shared libraries运行时多一步加载速度稍慢# 1. 生成位置无关代码 -fPIC必须 gcc -c -fPIC add.c -o add.o # 2. 生成动态库 libxxx.so gcc -shared add.o -o libadd.so # 3. 编译程序链接动态库 gcc main.c -o main -L. -ladd创建及使用add.h#ifndef __ADD_H__ #define __ADD_H__ int add(int a,int b); #endifadd.c#include add.h int add(int a,int b) { return ab; }main.c#include stdio.h #include add.h int main(void) { printf(1020%d\n, add(10,20)); return 0; }第 1 步生成位置无关代码-fPICgcc -c -fPIC add.c -o add.o-c只编译不链接生成目标文件 add.o-fPIC位置无关代码动态库必须加这个参数动态库会被加载到内存任意地址代码不能写死内存地址所以要 PIC。不加这个编译会报错第 2 步用 add.o 打包生成动态库 libadd.sogcc -shared add.o -o libadd.so-shared告诉 gcc 生成共享库动态库命名规则libxxx.soxxx 是库名后面-lxxx使用执行完当前目录就有libadd.so③ 3-2 动态库使用编译 main 程序链接动态库gcc main.c -o main_dynamic -L. -ladd解释-L.编译时在当前目录找库文件libadd.so-ladd链接 libadd.so自动省略 lib 和 .so✅ 到这里编译成功生成可执行文件main_dynamic问题现在直接运行./main_dynamic报错error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory❗核心概念区分很多人搞不懂-L.只是编译阶段给 gcc 看的编译的时候去当前目录找库程序运行阶段操作系统的动态链接器 ld-linux.so 不会默认去当前目录找 so 编译成功 ≠ 运行成功用ldd查看程序依赖的动态库ldd ./main_dynamic你会看到libadd.so not found4 种解决方案由简单到永久方案 1临时环境变量 LD_LIBRARY_PATH课堂测试首选临时生效# 把当前目录加入动态库搜索路径 export LD_LIBRARY_PATH$PWD # 再运行程序 ./main_dynamic✅ 输出102030缺点只在当前终端有效新开终端就失效。方案 2编译时写入 rpath推荐实验用把路径写进程序内部重新编译 main加上-Wl,-rpath$PWDgcc main.c -o main_dynamic -L. -ladd -Wl,-rpath$PWD-Wl,-rpath传递参数给链接器把库路径硬编码到程序中 之后直接运行./main_dynamic不用设置环境变量。方案 3修改系统配置文件全局永久将库路径写入/etc/ld.so.confsudo echo /root/lesson10 /etc/ld.so.conf更新系统动态库缓存sudo ldconfig所有用户、所有终端都可以找到这个 so永久生效。方案 4直接拷贝 so 到系统库目录sudo cp libadd.so /usr/lib系统默认搜索/usr/lib拷贝完成即可运行。静态库原理编译链接阶段把库里面的代码完整复制合并进可执行文件。运行时不再依赖原库文件。特点✅ 优点运行独立不需要系统上存在该库移植方便运行速度略快运行时不用加载库❌ 缺点可执行文件体积大多个程序用同一个静态库会有多份副本浪费磁盘库更新必须重新编译链接程序才能生效# 1. 源码编译成目标文件 .o gcc -c add.c -o add.o # 2. ar 打包成静态库 libxxx.a ar rcs libadd.a add.o # r:插入文件 c:创建库 s:建立索引 # 3. 使用静态库编译程序 gcc main.c -o main -L. -ladd # -L 指定库搜索路径-ladd 链接 libadd.a省略lib和.a创建使用静态库add.h#ifndef __ADD_H__ #define __ADD_H__ int add(int a, int b); #endifadd.c#include add.h int add(int a, int b) { return a b; }main.c#include stdio.h #include add.h int main(void) { int a 10; int b 20; printf(10 20 %d\n, add(10,20)); return 0; }静态库生成把add.c编译成目标文件 add.ogcc -c add.c -o add.o使用ar工具打包生成静态库libadd.aar rcs libadd.a add.or插入 / 替换文件c创建库文件s生成符号索引加快链接编译 main链接静态库gcc main.c -o main_static -L. -ladd-L.告诉编译器库文件在当前目录查找-ladd链接 libadd.a自动补全lib和.a运行./main_static输出10 20 30✅ 静态库特点编译完成后删除libadd.a程序依旧可以正常运行。代码已经拷贝进可执行文件。项目静态库 .a动态库 .so链接时机编译链接期运行期代码拷贝完整复制进程序只记录符号不复制文件体积大小内存占用多进程多副本内存共享一份更新库重新编译程序替换 so 即可运行依赖不依赖原库必须存在 so 文件扩展名.a.so面试题一、基础简答题必背1. 什么是静态库后缀原理答静态库.aLinux本质是一堆.o目标文件的归档包ar打包。 编译链接阶段静态库里面的代码会直接复制拷贝到你的可执行程序中。 程序运行时不再依赖静态库文件。优点运行独立不需要外部库部署简单。 缺点可执行文件体积大多个程序同时使用会有多份代码副本浪费内存库更新后程序必须重新编译。2. 什么是动态库共享库后缀原理答动态库.soLinux。 链接阶段只记录符号信息不拷贝代码等到程序运行的时候操作系统才把动态库加载进内存多个进程可以共享同一份库代码。优点程序体积小内存节省库升级替换程序不用重新编译。 缺点运行时依赖 so 文件缺少就无法运行部署要带上库。3. 静态库和动态库的区别高频面试大题答链接时机不同静态库编译链接阶段代码拷贝进可执行文件。 动态库编译只做符号检查运行时才加载库。文件依赖静态编译完成后.a库文件可以删除程序正常运行。 动态运行时必须存在.so否则程序启动失败。内存占用静态多个进程运行内存有多份库代码。 动态内存中只保留一份库多个进程共享。程序大小静态编译出来的可执行文件更大动态更小。更新方式静态库修改应用必须重新编译。 动态替换 so 文件应用不用重新编译。4. 静态库怎么生成、使用答#1.编译生成目标文件 gcc -c add.c -o add.o #2.ar打包生成静态库libadd.a ar rcs libadd.a add.o #3.使用静态库编译程序 gcc main.c -o main_static -L. -ladd-L.编译时库搜索路径-ladd链接 libadd 库5. 动态库怎么生成、使用为什么要-fPIC答#1.生成位置无关代码 gcc -c -fPIC add.c -o add.o #2.生成动态库 gcc -shared add.o -o libadd.so #3.链接程序 gcc main.c -o main_dynamic -L. -ladd-fPICPosition Independent Code位置无关代码。 动态库可以被加载到内存任意地址代码不能写死绝对内存地址必须生成 PIC 代码静态库不需要。原理 坑点题你实验踩过的坑面试最爱问6. 编译时-L和环境变量LD_LIBRARY_PATH的区别答-L编译链接阶段生效告诉 gcc 去哪里找库文件。LD_LIBRARY_PATH程序运行阶段生效告诉操作系统动态链接器去哪里找 so。所以编译成功 ≠ 运行成功。7. 动态库编译成功运行报错cannot open shared object file原因 解决方案答原因编译时-L指定路径只给编译器用运行时动态链接器不会默认去当前目录找 so。四种解决办法临时设置环境变量export LD_LIBRARY_PATH$PWD当前终端生效编译加-Wl,-rpath,路径把库路径写入程序内部修改/etc/ld.so.conf添加库路径执行sudo ldconfig更新缓存全局永久将 so 拷贝到系统默认库目录/usr/lib8. 同一个目录下同时存在 libadd.a 和 libadd.so执行gcc main.c -L. -ladd会链接静态还是动态库怎么强制静态链接答gcc优先链接动态库 libadd.so。 强制静态链接增加-static参数gcc main.c -o main_static -L. -ladd -static9.ldd命令作用答ldd 可执行文件名查看这个程序依赖哪些动态库以及库是否能找到。找不到会显示not found。10. ar 命令 rcs 参数含义答r替换 / 插入文件到库中c创建库文件s生成符号索引加快链接速度11. 动态链接的两种方式隐式链接、显式链接dlopen答隐式链接就是我们刚才实验这种编译时-lxxx链接库程序启动自动加载 so。 显式链接代码里面用dlopen()、dlsym()、dlclose()在代码里手动打开动态库运行时随时加载不需要编译时链接。插件化框架常用。12. 动态库的 rpath 是什么答rpath 是写入可执行程序内部的库搜索路径。程序运行时优先从 rpath 指定目录查找 so不需要配置环境变量。编译参数-Wl,-rpath/xxx/path
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进