c++之动态库与静态库
C++静态库 vs 动态库(DLL/SO),从底层原理完整讲透(Windows视角,VS2019,贴合你工控Qt项目)
先记住一句话:
静态库(.lib):把目标代码在编译链接阶段直接合并进你的exe;动态库(dll + import lib):exe里面只留下一个“引用标记”,真正代码在程序运行的时候才加载进来。
⚠️ Windows有两种lib,这是90%开发者混淆的根源:
1. 静态库 lib(Static Lib):包含真实机器码;
2. 导入库 lib(Import Lib,配合dll):没有代码!只有符号索引表,告诉链接器:某某函数在哪个dll里面,运行时去找它。
很多面试者栽在这里,把导入库lib当成静态库。
一、静态库 .lib(Static Library)原理
生成阶段
.cpp编译成.obj目标文件,ar/lib.exe工具把一堆obj打包压缩成一个lib文件,本质就是obj的压缩包。不做链接!没有解析外部符号。
链接阶段(最关键)
链接器(link.exe)扫描lib:凡是exe用到的函数/类,把对应的obj完整拷贝一份,嵌入exe内部;没有用到的obj,默认不拷贝。
✅产物exe是独立完整的,发布的时候不需要带这个lib,也不需要额外dll。
优点
1. 独立发布,零依赖:工控软件最重要的优势,不会出现客户机器缺库无法启动;
2. 运行时没有加载开销;
3. 版本不会冲突,代码已经固化到exe。
缺点(工控项目痛点)
1. exe体积膨胀;同一个库被多个exe使用,每个exe内部复制一份代码,内存重复占用;
2. 库升级非常麻烦:比如修复bug,所有可执行程序全部要重新编译链接一遍,不能单独替换;
这个点决定:插件架构绝对不能用纯静态库实现。静态库做不到热替换。
举例子:xxx工具,如果DDS做成静态库编译进主程序,DDS修一个bug,整个主程序全部重编重发。
二、动态库 DLL + 导入库 lib(Import lib)原理
动态库分两种加载方式:隐式链接(加载时加载)、显式加载(运行时手动LoadLibrary),也就是你Qt插件系统的根基。
1)隐式链接(日常SDK模式:h + lib(import‑lib)+dll)
• 编译dll:编译器编译dll项目,生成dll(真正二进制代码) + import lib(符号索引表,很小)
• 编译你的exe:
◦ 头文件.h告诉编译器函数声明;
◦ import lib给链接器记录:这个函数属于哪个DLL,但是不拷贝任何机器码进exe!
◦ exe里面生成一张导入表(Import Table),写着程序启动要加载哪些dll,调用哪些函数。
• 运行时(操作系统加载器干活):Windows启动exe,读取导入表,自动去磁盘找到对应dll,映射进进程内存,绑定函数地址,程序才开始跑。
✅这就是常规SDK交付形态(h + lib + dll),你之前问过的SDK交付。
隐式链接的致命弱点:dll缺失,程序直接启动失败,打不开(工控现场经常遇到)
2)显式加载 LoadLibrary / GetProcAddress(Qt原生插件模式,你最近一直在研究)
不需要头文件,不需要import lib!只要一个dll文件
程序跑起来以后,主动调用LoadLibrary加载dll,GetProcAddress按字符串名字拿到函数地址调用。
👉这就是插件系统的底层基础:宿主程序事先不知道有哪些dll,运行时扫描文件夹,动态加载插件dll。
Qt的QPluginLoader本质就是对LoadLibrary/GetProcAddress做了面向对象封装,加了IID校验、元对象信息。
⚠️但是注意:原生Windows DLL只导出C风格函数;C++类名字会名字粉碎(name mangling),GetProcAddress拿不到,所以Qt插件要靠Q_DECLARE_INTERFACE + Q_PLUGIN_METADATA宏解决这个问题,这个是Qt插件最核心的底层机制。
三、核心对比表
维度 静态库(.lib,真正静态库) 动态库DLL(隐式链接) 动态库DLL(显式加载/插件)
代码落地位置 拷贝进exe内部 代码放在外部dll,exe保存导入表 代码放在外部dll,运行时手动加载
什么时候解析符号 链接期 程序启动瞬间 程序运行中按需加载
发布交付物 只要exe exe + dll exe + dll插件,无lib无h
升级方式 全部程序重编重发 直接替换dll,exe不用重编 直接替换插件dll,甚至可以卸载再加载
适合架构 小型独立工具,追求零依赖 SDK模块,公共基础库 插件化架构(你的工控项目)
缺点 体积大,不能单独升级dll dll缺失直接崩溃启动失败 接口约束全靠自己维护,没有编译器校验,出错更隐蔽
四、面试高频追问(工控C++岗位最爱问)
Q1:为什么静态库不能做插件?
静态库的代码链接期就合并进exe了;运行期不能新增、不能卸载模块。插件要求运行时加载卸载模块,只能走动态库显式加载。
Q2:Windows下有lib就一定是静态库吗?
不是!lib分静态库lib(带代码)和导入库lib(只有符号表,配合dll)。很多人踩坑。怎么区分:看VS项目配置,项目类型是静态库还是动态库。
Q3:动态库的代码,多个exe调用,内存是几份?
DLL代码段只读,操作系统物理内存里面只有一份,多个进程共享同一份物理内存,这是动态库最大的优势;数据段每个进程一份。静态库是每个exe一份完整代码。
Q4:C++跨dll边界传递对象,为什么经常崩溃?(工控项目超级高频bug)
两套堆!dll内部new出来的对象,不能在exe里面delete;exe new出来的对象不能在dll delete。两边CRT堆可能不是同一个堆。这也是为什么很多大型SDK接口只用C风格接口,传基础类型,不直接传递C++对象。
延伸:PIMPL模式很大一个用途就是规避跨dll传递完整C++类带来的ABI不稳定问题。这个刚好串起你前面学的PIMPL。
Q5:动态库版本冲突是什么?(DLL‑HELL)
Windows根据搜索路径加载dll,如果机器上有同名但是版本不同的dll,加载了错误版本dll,程序直接崩溃。工控部署最头疼的问题,解决方法是私有部署,dll放在exe同级目录。
五、结合你当前项目做一个总结(面试口述版)
“我们工控xxx系统采用插件架构,模块交付为dll,采用运行时动态加载。
基础公共模块比如工具库、数学库我们编译成静态库打进主程序,避免现场dll依赖缺失;
但是实体插件模块采用动态库显式加载,这样后续新增实体插件,不需要重新编译主程序,可以直接部署dll。
同时为了规避C++跨DLL ABI不稳定,插件之间只通过抽象接口交互,也就是Qt插件接口模式。”
如果你愿意,我下一步可以给你讲动态库的名字粉碎机制、extern C到底解决什么问题;以及为什么Qt插件不用extern C也可以导出C++接口。这个是这条线最深入的一步。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)