一个很奇怪的 c++ 项目编译问题
这是一段对 GPU 数据库 Megawise 开发期间经历的一次回顾与总结。时间稍微有点久远,可能有些细节不是特别清楚。
起因
在一个代码百万行+的 C++ 项目中,所依赖的第三方库那必定是纷繁复杂。当时的项目中,大概拆分成了9个模块,有独立库包装了一些系统的基础模块,也有大量与数据库业务相关的优化器、调度器、执行引擎、存储代码。
起因编译问题就是存储模块所依赖的一系列库的版本和依赖关系问题导致的。
该问题大概简化为如下:
有三方库 A、B, A 为静态库, B 是动态库,B 链接了 A,
有系统模块 C、D, C是动态库(可修改),C 链接了 B; D 是可执行文件,链接了 A、C。
依赖关系还是挺明确的,但是此种依赖关系下,会报出类似 A 中函数找不到的错误。不管如何调节 D 链接 A、C 的顺序都不行。
经分析,可能是内存中存在两个版本的 A 库,其中 B 链接的是 A 中的一部分,而 D 中所需的另一部分在这一部分找不到。
其他类似的分析如 C 改成静态库,修改 B 中的依赖关系,都进行了尝试,但最后的结果好像都没有解决。
最后的解决方案是,主要在编译 C 动态库的时候不对 B 进行链接,改到在编译可执行文件的链接阶段一起链接,则问题解决。
很迷的一个问题,但类似的多版本的库依赖问题,在 C++ 的编译问题中还是很常见的(据同事描述)。
其实在最后的阶段链接上所有动态库需要的库(链接库依旧如常),还有一个优势就是,下面的依赖的修改只要不涉及接口,便不需要重新编译上层的动态库,可以有效加快编译进度。
更改后的编译策略
重复梳理一下该策略:
- 静态库在编译时只链接需要的静态库,不对动态库进行链接;
- 动态库在编译时只链接需要的动态库(还是全部不链接,记不清了);
- 编译可执行文件的时候将所需要的库全部链接上。
- 单元测试的可执行文件,需要将所需要的库全部链接,保证不会出现更换版本后接口对不上、函数为定义等问题。
在这样的策略下,解决了问题的同时,也提升了编译的进度。
编译流程优化
另一个编译流程的优化和这个问题其实已经没有太大的关系了,只是在统一了 CI 编译和本地缺少权限的编译流程后做出的一套简单的编译框架;使得多模块的编译和调试更灵活、方便。
不过该优化在短暂的运行时间内(项目暂停),还是深受同事们的喜欢。