

模块之间的依赖关系,通常被理解成一棵树:主程序在顶上,往下分出各个依赖。
但实际的依赖关系经常不是树,而是一张网——甚至可能绕成环。
两个模块互相依赖,或者三个模块首尾相连成一个圈:A 用到 B,B 用到 C,C 又回来用到 A。
这种结构会遇到一些不太直观的情况。最反直觉的一点是:它通常能加载成功——但能不能正确工作,是另一回事。
直接的循环:甲模块的导入表里有乙,乙的导入表里又有甲。
间接的循环:甲依赖乙,乙依赖丙,丙又依赖甲。三个模块未必都意识到自己在一个环里。
还有一种更隐蔽的:静态层面(导入表)不构成环,但初始化时互相调用构成环。
示意一下后面这种:
// a.cpp: A_Init calls into B
void A_Init() { B_DoSomething(); }
// b.cpp: B_Init calls back into A
void B_Init() { A_DoSomething(); }两个模块在编译期只看到对方的声明,链接层面也许能通过;但一运行,两边都在等对方先准备好。
先看最容易被误解的一点。
加载模块时,系统要递归地解析依赖:解析甲的导入,发现需要乙;于是去加载乙;解析乙的导入,又看到甲。
如果这里不做处理,就会无限递归下去。
所以加载器需要一份"当前正在加载中"的记录:解析过程中遇到一个已经在处理中的模块时,它不会重新走一遍,而是直接跳过——因为那个模块已经在处理队列里了。
这个保护带来的结果是:静态层面的循环依赖通常不会让加载失败。 环会被"绕开",所有模块最后都能进到进程里。
所以"程序能启动"这件事,不能用来证明依赖关系是健康的。
加载能完成,但接下来还有一步:每个模块都要执行自己的初始化。
而这一步上,循环依赖的代价就显现了。
假设甲和乙互相依赖,那么:
于是出现的结果是:这次能跑,下次未必。
这不是"偶发故障",而是"顺序没被保证"的必然表现。
它可能表现为:读到还没赋值的全局状态、某个初始化只做了一半、或者在特定情况下直接崩掉。而错误的位置往往离真正的原因很远。
这是循环依赖最误导人的地方。
因为实际顺序受好几件事影响:
这些条件在开发机上往往恰好凑成一个"能跑"的组合,而到了另一台机器、或者换一条启动路径,组合就变了。
所以这类问题的特征非常明确:偶发、与机器或启动路径相关、单独测各个模块都正常。
顺带说一句:这和前面讲过的"一个进程里同名模块只能有一份"是两类不同的问题——那个是"名字被占",这个是"顺序无法同时满足"。两者的共同点是:都不能靠替换文件解决。
第一条,把公共部分抽出去。
如果两个模块互相依赖,通常意味着它们共用了某些东西。把这部分独立成第三个模块,让原来两边都依赖它,环就断了。
这是最干净的解法,但它要求代码结构允许重构。
第二条,把其中一条边改成延迟加载。
让一方在真正用到时才去加载另一方。这样在加载阶段,环就不成立了。
代价有两个:第一次调用时会有一次加载开销;以及失败时机被推迟了——原本加载阶段就能发现的缺失,变成了运行到某一步才出问题。
第三条,改成"按名字取函数"。
不在编译期建立依赖,而是运行时去取对方的函数地址。
最灵活,但代价最高:名字写错、函数不存在、签名对不上,这些编译期本该拦住的问题,全都变成了运行期问题,需要自己处理。
三条路的取舍是一致的:越干净的解法越需要重构,越省事的解法把风险推得越晚。
两个特征,凑齐就值得怀疑:
确认的方法是看依赖关系:分别列出参与的两个模块各自的依赖,看对方的名字是否出现在里面;间接的环需要多看一层。
这里有一个判断上的提醒:这类问题和"文件缺失"的表现完全不同——文件一个不少,也不会有"找不到模块"的报错。它表现出来的是"行为不对",而不是"启动不了"。
所以 DLL修复工具 在这里没有可以下手的对象:它能检查的是清单是否齐全、常见组件有没有缺、登记是否异常;而这里的每个文件都在,版本也对,出问题的是它们之间的依赖结构与顺序——这也是前面几篇反复提到的那条边界:工具的射程止于文件层。
这类问题要在代码结构上解决——抽公共部分、改延迟加载、或者改成运行期获取。补文件改变不了两个模块“互相等对方”这个事实,这也是 DLL修复工具 在这一层帮不上忙的原因。
关于 DLL修复工具 处理不了的那一类"文件都在却行为不对",记住四条:
这里可以带走的经验是关于"顺序"的:很多设计隐含着"某件事一定在另一件事之前发生"这个前提,而这个前提往往没有被任何机制保证。
正常的时候它刚好成立,于是没人注意到;一旦环境变了,它就变成了一个偶发故障。 循环依赖只是把这种隐含前提暴露得最明显的一种写法——遇到"有时对有时不对"的问题时,先找一找有没有这种没人保证的顺序假设。
https://www.ijinshan.com/functions/repairdll.html?channel=4098
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。