ARC环境下TNetEncoding.GetBase64Encoding是否存在内存泄漏?我的修改正确吗?
关于TNetEncoding.GetBase64Encoding内存泄漏的判断与修改分析
你的判断完全正确,原代码确实存在内存泄漏风险,而且你的修改方向是对的,只是需要调整几个语法细节让代码能正常编译并正确工作。
原代码的问题所在
原代码试图用AtomicCmpExchange实现线程安全的单例初始化,但在非ARC(自动引用计数)环境下,引用计数的处理逻辑有漏洞:
class function TNetEncoding.GetBase64Encoding: TNetEncoding; var LEncoding: TBase64Encoding; begin if FBase64Encoding = nil then begin LEncoding := TBase64Encoding.Create; if AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) <> nil then LEncoding.Free; {$IFDEF AUTOREFCOUNT} FBase64Encoding.__ObjAddRef; {$ENDIF AUTOREFCOUNT} end; Result := FBase64Encoding; end;
问题出在条件编译块的执行时机:
- 当
AtomicCmpExchange返回非nil时,说明其他线程已经抢先初始化了FBase64Encoding,我们创建的LEncoding会被正确释放,但此时代码仍然会对FBase64Encoding(其他线程的实例)调用__ObjAddRef。 - 这个额外的引用计数永远不会被释放——因为类变量
FBase64Encoding是全局持有的,不会主动减少引用,最终导致该实例的内存无法被回收,造成泄漏。
你的修改的正确性(及小调整)
你把__ObjAddRef移到了else分支(对应AtomicCmpExchange返回nil的情况),这个逻辑是完全正确的,只需要修正两个小细节:
class function TNetEncoding.GetBase64Encoding: TNetEncoding; var LEncoding: TBase64Encoding; begin if FBase64Encoding = nil then begin LEncoding := TBase64Encoding.Create; if AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) <> nil then LEncoding.Free else {$IFNDEF AUTOREFCOUNT} // 改为非ARC环境下才执行 FBase64Encoding.__ObjAddRef; {$ENDIF} end; Result := FBase64Encoding; end;
调整说明:
- 把
{$IFDEF AUTOREFCOUNT}改成{$IFNDEF AUTOREFCOUNT}——因为ARC环境下,编译器会自动管理对象的引用计数,不需要手动调用__ObjAddRef;只有非ARC环境下才需要手动维护引用计数。 - 把占位符
!!!ELSE!!!替换成标准的Delphielse关键字,否则代码无法编译。
这样修改后:
- 当
AtomicCmpExchange成功(返回nil)时,我们创建的LEncoding被赋值给FBase64Encoding,此时在非ARC环境下手动增加引用计数,确保实例能被正确持有。 - 当
AtomicCmpExchange失败时,我们只释放自己创建的LEncoding,不会对其他线程的实例做多余的引用操作,避免了内存泄漏。
总结
- 你的判断(原代码存在内存泄漏)是准确的,问题源于非ARC环境下错误地给其他线程创建的实例增加了未释放的引用计数。
- 你的修改方向完全正确,修正语法细节后,就能彻底解决这个内存泄漏问题。
内容的提问来源于stack exchange,提问作者zeus
相关产品推荐
相关产品推荐

