为什么我会在 EOutOfResources 中泄漏内存?

WeG*_*ars 2 delphi jpeg delphi-10.3-rio

我尝试围绕这个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;
Run Code Online (Sandbox Code Playgroud)

内存块已泄漏。尺寸为:36

此块由线程 0xD0C 分配,当时的堆栈跟踪(返回地址)为: 407246 40830F 408ADE 43231B [__dbk_fcall_wrapper 处的未知函数] 407246 40A532 53C353 [Unknown function at __dbk_fcall_wrapper] 407246 40A532 53C353 [Unknown function at TMethodnknown0FmplationEmple6Rceptation6R 40830F 408ADE 43231B ] 77656494 [RtlNtStatusToDosError] 767A7BEA [IsNLSDefinedString 处的未知函数]

该块当前用于类的对象:EOutOfResources 分配编号为:4181

从指针地址 7EEEA6C0 开始的 256 字节的当前内存转储:74 7F ...... t D 。ü $ ......

内存块已泄漏。大小为:132
该块由线程 0xD0C 分配,当时的堆栈跟踪(返回地址)为: 407246 40A2E7 40A518 53C341 [Unknown function at TMethodImplementationIntercept] 6E006F [Unknown function at TMethodImplementation765R75R55R55R75R75R3Error]ErrorsNormal ] 767A7BEA [IsNLSDefinedString 处的未知函数] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery] 898FD9 [GetFrameBasedStackTrace]

该块当前用于类的对象:UnicodeString

分配号为:4180

从指针地址 7EFA24F0 开始的 256 字节的当前内存转储:B0 04 02 00 01 00 00 00.......... . . . . . . :。. . 不是 。. 足够的存储空间。. 一世 。。. 一种 。诉。一种 。一世 。升。一种 。乙。升。电子。. 吨。哦。. 。r 。哦。C 。电子。。。. 吨。H 。一世 。。. C 。哦。米。米。一种 。名词。d…………

此应用程序已泄漏内存。小块泄漏是(不包括指针注册的预期泄漏):

21 - 36 字节:EOutOfResources x 1 117 - 132 字节:UnicodeString x 1

为什么它会在那里泄漏内存?

Rem*_*eau 5

正如@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 错误引发,这与堆无关。

  • EOutOfResourcesAllowFree当它应该是 True 时,它的标志设置为 False 。并且标志是private,所以它不能被除SysUtils单元覆盖,它只在完成过程中对 2 个单例进行覆盖。所以基本上,所有 EHeapException派生的异常都会在运行时泄漏。

  • RegisterExpectedMemoryLeak()AllowFree为 False时,单例以及所有其他后代实例不会传递给 RTL 的函数,因此可以从泄漏报告中省略它们。

这个泄漏问题从 Delphi 5 开始就存在,并且已经报告给 Embarcadero:

RSP-17193:EOutOfResources 内存泄漏

RSP-19737:EOutOfResources 异常导致内存泄漏