共享库与模块库的区别及起源解析请求
核心定义与本质差异
- 共享库:是为编译/加载时绑定设计的库文件,核心作用是提供程序运行必需的基础功能。它会被操作系统动态链接器(如
ld.so)自动加载到进程地址空间,符号(函数、变量)在链接阶段就会被解析或标记为延迟解析。 - 模块库:专为运行时动态加载而生,用于实现程序的功能扩展(如插件系统)。它无需在编译阶段与程序链接,而是通过
dlopen()、dlsym()等函数手动加载,符号解析完全在运行时按需完成。
技术层面的关键区别
- 链接与加载逻辑
- 共享库:编译时需通过
gcc -lxxx指定链接,生成的可执行文件会包含依赖信息,运行时由动态链接器自动触发加载。 - 模块库:无需编译时链接,程序通过
dlopen("./xxx.so", RTLD_LAZY)这类调用手动加载,符号需通过dlsym()主动查找。
- 共享库:编译时需通过
- 符号可见性设计
- 共享库默认导出所有符号(可通过
-fvisibility=hidden调整),方便链接阶段的符号解析。 - 模块库通常仅显式导出特定插件接口符号,多通过
__attribute__((visibility("default")))标记,避免全局符号冲突。
- 共享库默认导出所有符号(可通过
- 加载时机
- 共享库:要么在程序启动时加载(默认),要么通过延迟加载(
RTLD_LAZY)在首次调用对应符号时加载。 - 模块库:完全由程序在运行时按需加载,可随时加载或卸载。
- 共享库:要么在程序启动时加载(默认),要么通过延迟加载(
历史起源
- 共享库的起源:最早诞生于上世纪80年代的UNIX系统,初衷是解决静态库导致的可执行文件体积过大、内存冗余问题。System V和BSD系统先后实现动态链接机制,核心是让多个进程共享同一份库的内存映射,大幅节省系统资源。
- 模块库的起源:随着插件化需求兴起(如文本编辑器插件、桌面环境扩展),单纯的共享库链接无法满足动态扩展需求。基于
dlopen()系列函数的模块加载机制随之出现,允许程序在运行时加载未预链接的库,实现功能的灵活扩展。早期X Window系统插件、Apache模块都是模块库的典型应用场景。
同一.so文件既可以链接又可以加载的原因
*nix系统中,.so本质是ELF格式的共享对象文件,并没有严格的“共享库”或“模块库”标记。二者的区分更多是设计意图和使用场景的不同:
- 按共享库设计的
.so,只要导出了清晰的接口,就能通过dlopen()加载调用; - 按模块库设计的
.so,如果所有依赖符号都能被链接器找到,也可以在编译时直接链接。
CMake中区分二者,是为了生成适配不同场景的构建规则(如链接选项、安装路径),但底层文件格式并无本质差异。
跨系统差异的优化方向
若需扩展到其他系统,可针对以下差异细化问题:
- Windows:共享库为
.dll,模块库通常也是.dll,加载使用LoadLibrary()、GetProcAddress(),符号导出规则和链接机制与*nix不同; - macOS:共享库为
.dylib,模块库可能是.bundle或.dylib,加载仍用dlopen()系列函数,但符号解析、库搜索路径有专属规则。
内容的提问来源于stack exchange,提问作者StoneThrow
相关产品推荐
相关产品推荐

