为什么我会在 EOutOfResources 中泄漏内存?
Why do I leak memory in EOutOfResources?
我尝试围绕 this 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;
A memory block has been leaked. The size is: 36
This block was allocated by thread 0xD0C, and the stack trace (return
addresses) at the time was: 407246 40830F 408ADE 43231B [Unknown
function at __dbk_fcall_wrapper] 407246 40A532 53C353 [Unknown
function at TMethodImplementationIntercept] 6E006F [Unknown function
at TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError]
77656494 [RtlNtStatusToDosError] 767A7BEA [Unknown function at
IsNLSDefinedString]
The block is currently used for an object of class: EOutOfResources
The allocation number is: 4181
Current memory dump of 256 bytes starting at pointer address 7EEEA6C0:
74 7F............ t D . ü $ ú ......
A memory block has been leaked. The size is: 132
This block was allocated by thread 0xD0C, and the stack trace (return
addresses) at the time was: 407246 40A2E7 40A518 53C341 [Unknown
function at TMethodImplementationIntercept] 6E006F [Unknown function
at TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError]
77656494 [RtlNtStatusToDosError] 767A7BEA [Unknown function at
IsNLSDefinedString] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery]
898FD9 [GetFrameBasedStackTrace]
The block is currently used for an object of class: UnicodeString
The allocation number is: 4180
Current memory dump of 256 bytes starting at pointer address 7EFA24F0:
B0 04 02 00 01 00 00 00.........
. . . . . . . : . . . Not . . enough storage . . i . s . . a . v . a . i . l . a . b
. l . e . . t . o . . p . r . o . c . e . s
. s . . t . h . i . s . . c . o . m . m . a
. n . d ............
This application has leaked
memory. The small block leaks are (excluding expected leaks registered
by pointer):
21 - 36 bytes: EOutOfResources x 1
117 - 132 bytes: UnicodeString x 1
为什么会在那里泄漏内存?
正如@kami 在评论中提到的,EHeapException
有一个默认为 False 的内部 AllowFree
标志,防止 EHeapException
的实例被异常处理程序释放。
EOutOfResources
派生自 EOutOfMemory
,后者又派生自 EHeapException
.
SysUtils
单元有 2 个类型为 EOutOfMemory
和 EInvalidPointer
的单例对象。每当 RTL 直接引发这两种特定异常类型时,它每次都会引发那些 类 的 相同实例 。所以他们有一个 AllowFree
标志来防止异常处理程序释放单例。当 SysUtils
单元完成时,单例被释放。
这实际上是记录在案的行为:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EHeapException
Note: Memory for these exceptions is pre-allocated whenever an application starts and remains allocated as long as the application is running. Never raise EHeapException
or its descendants directly.
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EOutOfMemory
Memory for the EOutOfMemory
exception is pre-allocated whenever an application starts and remains allocated as long as the application is running.
Note: Never raise EOutOfMemory
directly. Instead, call the global OutOfMemoryError
procedure.
然而,尽管 EOutOfResources
派生自 EHeapException
,但它从未以单例方式使用,因此它的 AllowFree
标志确实不应该为 False。所以在我看来这里有几个错误:
EOutOfResources
并不是真正的堆错误,不应该从 EHeapException
开始。它实际上是一个常见的异常,例如 Vcl.Graphics
单元为其一些 GDI 错误引发 EOutOfResources
,这与堆无关。
EOutOfResources
将其 AllowFree
标志设置为 False,而它应该是 True。标志是 private
,所以它不能被覆盖,除非 SysUtils
单元,它只在完成期间对 2 个单例进行覆盖。所以基本上,所有 EHeapException
派生的异常都会在运行时泄漏。
当 AllowFree
为 False 时,单例以及所有其他后代实例不会传递给 RTL 的 RegisterExpectedMemoryLeak()
函数,因此可以从泄漏报告中省略它们。
此泄漏问题自 Delphi 5 以来就存在,并且已报告给 Embarcadero:
我尝试围绕 this 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;
A memory block has been leaked. The size is: 36
This block was allocated by thread 0xD0C, and the stack trace (return addresses) at the time was: 407246 40830F 408ADE 43231B [Unknown function at __dbk_fcall_wrapper] 407246 40A532 53C353 [Unknown function at TMethodImplementationIntercept] 6E006F [Unknown function at TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [Unknown function at IsNLSDefinedString]
The block is currently used for an object of class: EOutOfResources The allocation number is: 4181
Current memory dump of 256 bytes starting at pointer address 7EEEA6C0: 74 7F............ t D . ü $ ú ......
A memory block has been leaked. The size is: 132
This block was allocated by thread 0xD0C, and the stack trace (return addresses) at the time was: 407246 40A2E7 40A518 53C341 [Unknown function at TMethodImplementationIntercept] 6E006F [Unknown function at TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [Unknown function at IsNLSDefinedString] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery] 898FD9 [GetFrameBasedStackTrace]The block is currently used for an object of class: UnicodeString
The allocation number is: 4180
Current memory dump of 256 bytes starting at pointer address 7EFA24F0: B0 04 02 00 01 00 00 00......... . . . . . . . : . . . Not . . enough storage . . i . s . . a . v . a . i . l . a . b . l . e . . t . o . . p . r . o . c . e . s . s . . t . h . i . s . . c . o . m . m . a . n . d ............
This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):
21 - 36 bytes: EOutOfResources x 1 117 - 132 bytes: UnicodeString x 1
为什么会在那里泄漏内存?
正如@kami 在评论中提到的,EHeapException
有一个默认为 False 的内部 AllowFree
标志,防止 EHeapException
的实例被异常处理程序释放。
EOutOfResources
派生自 EOutOfMemory
,后者又派生自 EHeapException
.
SysUtils
单元有 2 个类型为 EOutOfMemory
和 EInvalidPointer
的单例对象。每当 RTL 直接引发这两种特定异常类型时,它每次都会引发那些 类 的 相同实例 。所以他们有一个 AllowFree
标志来防止异常处理程序释放单例。当 SysUtils
单元完成时,单例被释放。
这实际上是记录在案的行为:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EHeapException
Note: Memory for these exceptions is pre-allocated whenever an application starts and remains allocated as long as the application is running. Never raise
EHeapException
or its descendants directly.
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.EOutOfMemory
Memory for the
EOutOfMemory
exception is pre-allocated whenever an application starts and remains allocated as long as the application is running.Note: Never raise
EOutOfMemory
directly. Instead, call the globalOutOfMemoryError
procedure.
然而,尽管 EOutOfResources
派生自 EHeapException
,但它从未以单例方式使用,因此它的 AllowFree
标志确实不应该为 False。所以在我看来这里有几个错误:
EOutOfResources
并不是真正的堆错误,不应该从EHeapException
开始。它实际上是一个常见的异常,例如Vcl.Graphics
单元为其一些 GDI 错误引发EOutOfResources
,这与堆无关。EOutOfResources
将其AllowFree
标志设置为 False,而它应该是 True。标志是private
,所以它不能被覆盖,除非SysUtils
单元,它只在完成期间对 2 个单例进行覆盖。所以基本上,所有EHeapException
派生的异常都会在运行时泄漏。当
AllowFree
为 False 时,单例以及所有其他后代实例不会传递给 RTL 的RegisterExpectedMemoryLeak()
函数,因此可以从泄漏报告中省略它们。
此泄漏问题自 Delphi 5 以来就存在,并且已报告给 Embarcadero: