delphi - 为什么我会在 EOutOfResources 中泄漏内存?
问题描述
我尝试围绕这个jpeg 解码器库(由 Arnaud Bouchez 原创)实现一个包装器。该库速度很快,但它不支持所有 jpeg!
对于非常大的 jpg 文件,它会失败(如预期的那样)并出现 EOutOfResources 异常。
所以我尝试默默地跳过这些文件。它可以工作,但是当我关闭应用程序时,FastMM 表示内存泄漏。
function FastJpgDecode(FileName: string; OUT ErrorType: string): TBitmap;
var Img: PJpegDecode;
res: TJpegDecodeError;
Stream: TMemoryStream;
begin
Result:= NIL;
Stream:= TMemoryStream.Create;
TRY
if Length(FileName) > MAX_PATH then { TMemoryStream does not support long paths }
begin
ErrorType:= 'File name too long!';
Exit;
end;
Stream.LoadFromFile(FileName);
Stream.Position:= 0;
res:= JpegDecode(Stream.Memory, Stream.Size, Img);
case res of
JPEG_SUCCESS:
begin
try
Result:= Img.ToBitmap; // This will raise an EOutOfResources for large files!
except
on EOutOfResources do
ErrorType:= 'JPEG_OUTOFMEM!';
end;
end;
JPEG_EOF : ErrorType:= 'JPEG_EOF!';
JPEG_OUTOFMEM : ErrorType:= 'JPEG_OUTOFMEM!';
JPEG_CPUNOTSUPPORTED : ErrorType:= 'JPEG_CPUNOTSUPPORTED!';
JPEG_BADFILE : ErrorType:= 'JPEG_BADFILE!';
JPEG_FORMATNOTSUPPORTED : ErrorType:= 'JPEG_FORMATNOTSUPPORTED!'; // Not all jpegs are supported. In this case we fall back to WIC or the standard LoadGraph loader (WIC).
end;
FINALLY
Img.Free;
Stream.Free;
END;
end;
function TJpegDecode.ToBitmap: TBitmap;
begin
if @self=nil
then result := nil
else
begin
result := TBitmap.Create;
try
if not ToBitmap(result) // This will raise an EOutOfResources for large files!
then FreeAndNil(result);
except
FreeAndNil(Result);
raise;
end;
end;
end;
内存块已泄漏。尺寸为:36
该块由线程 0xD0C 分配,当时的堆栈跟踪(返回地址)为: 407246 40830F 408ADE 43231B [Unknown function at __dbk_fcall_wrapper] 407246 40A532 53C353 [Unknown function at TMethodImplementationIntercept] 6E006F [Unknown function at TMethodImplementationFIntercept] 6E006F [Unknown function at TMethodImplementationRtl7Nt7 ] 77656494 [RtlNtStatusToDosError] 767A7BEA [IsNLSDefinedString 处的未知函数]
该块当前用于类对象:EOutOfResources 分配编号为:4181
从指针地址 7EEEA6C0: 74 7F............ t D 开始的 256 字节的当前内存转储。ü$ú……
内存块已泄漏。大小为:132
此块由线程 0xD0C 分配,当时的堆栈跟踪(返回地址)为:407246 40A2E7 40A518 53C341 [Unknown function at TMethodImplementationIntercept] 6E006F [Unknown function at TMethodImplementationIntercept] 7765648F [RtlNtStatusError499 [TStatusToDosError] 7765656 ] 767A7BEA [IsNLSDefinedString 处的未知函数] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery] 898FD9 [GetFrameBasedStackTrace]该块当前用于类对象:UnicodeString
分配编号为:4180
从指针地址 7EFA24F0 开始的 256 字节的当前内存转储:B0 04 02 00 01 00 00 00............ . . . . . . : . . . 不是 。. 足够的存储空间。. 一世 。小号 . 一种 。诉 一种 。一世 。升。一种 。乙。升。e. . 吨。○。. 磷。r 。○。C 。e. 小号 小号 . 吨。H 。一世 。小号 . C 。○。米。米。一种 。ñ。d …………
此应用程序已泄漏内存。小块泄漏是(不包括指针注册的预期泄漏):
21 - 36 字节:EOutOfResources x 1 117 - 132 字节:UnicodeString x 1
为什么它会在那里泄漏内存?
解决方案
正如@kami 在评论中提到的,默认情况下EHeapException
有一个内部AllowFree
标志为 False,防止实例EHeapException
被异常处理程序释放。
EOutOfResources
派生自EOutOfMemory
,而后者又派生自EHeapException
。
该SysUtils
单元有 2 个类型为EOutOfMemory
和的单例对象EInvalidPointer
。每当 RTL 直接引发这两种特定的异常类型时,它每次都会引发这些类的相同实例。所以他们有一个AllowFree
标志来防止异常处理程序释放单例。当SysUtils
单元完成时,单例被释放。
这实际上是记录在案的行为:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EHeapException
注意:只要应用程序启动,这些异常的内存就会预先分配,并且只要应用程序正在运行,就会一直保持分配状态。切勿直接抚养
EHeapException
或其后代。
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EOutOfMemory
EOutOfMemory
每当应用程序启动时,都会预先分配用于异常的内存,并在应用程序运行期间保持分配状态。注意:切勿
EOutOfMemory
直接加注。相反,调用全局OutOfMemoryError
过程。
然而,虽然EOutOfResources
派生自EHeapException
,但它从不以单例方式使用,所以它的AllowFree
标志真的不应该是 False。所以在我看来,这里有几个错误在起作用:
EOutOfResources
并不是真正的堆错误,不应该从EHeapException
一开始就派生出来。这实际上是一个常见的异常,例如Vcl.Graphics
单元引发EOutOfResources
了一些与堆无关的 GDI 错误。EOutOfResources
将其AllowFree
标志设置为 False 当它应该为 True 时。并且标志是private
,所以它不能被SysUtils
单元覆盖,除了在最终确定期间,它只对 2 个单例这样做。所以基本上,所有EHeapException
派生的异常都会在运行时泄露。当为 False 时,单例以及所有其他后代实例不会传递给 RTL 的
RegisterExpectedMemoryLeak()
函数,AllowFree
因此它们可以从泄漏报告中省略。
这个泄漏问题从 Delphi 5 开始就存在,并且已经报告给 Embarcadero: