【问题标题】:Why do I leak memory in EOutOfResources?为什么我会在 EOutOfResources 中泄漏内存?
【发布时间】:2020-03-13 12:38:53
【问题描述】:

我尝试围绕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;

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

这个块是由线程 0xD0C 分配的,堆栈跟踪(返回 地址)当时是:407246 40830F 408ADE 43231B [未知 __dbk_fcall_wrapper 处的函数] 407246 40A532 53C353 [未知 TMethodImplementationIntercept 处的函数] 6E006F [未知函数 在 TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [未知函数在 IsNLSDefinedString]

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

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

内存块已泄漏。尺寸为:132
该块由线程 0xD0C 分配,堆栈跟踪(返回 地址)当时是:407246 40A2E7 40A518 53C341 [未知 TMethodImplementationIntercept 处的函数] 6E006F [未知函数 在 TMethodImplementationIntercept] 7765648F [RtlNtStatusToDosError] 77656494 [RtlNtStatusToDosError] 767A7BEA [未知函数在 IsNLSDefinedString] 7677F0BA [VirtualQueryEx] 7677F177 [VirtualQuery] 898FD9 [GetFrameBasedStackTrace]

该块当前用于类对象:UnicodeString

分配号为:4180

从指针地址 7EFA24F0 开始的 256 字节的当前内存转储: B0 04 02 00 01 00 00 00………… . . . . . . . : . . .不是 。 .足够的存储空间。 .一世 。小号.一种 。诉一种 。一世 。升。一种 。 b .升。 e. .吨。 ○。 .磷。 r 。 ○。 C 。 e. s .小号.吨。 H 。一世 。小号. C 。 ○。米。米。一种 . ñ。 d ....

此应用程序已泄露 记忆。小块泄漏是(不包括注册的预期泄漏 通过指针):

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

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

【问题讨论】:

  • 看起来有点像异常/错误返回的数据,最终变得孤立。
  • EOutOfMemory - 非常特殊的例外。它包含私有标志AllowFree(默认为 False)。此标志防止创建的异常正常释放。见EHeapException.FreeInstance。这是“正常”的,因为当“正常”内存管理失败时会引发此异常

标签: delphi jpeg delphi-10.3-rio


【解决方案1】:

正如 @kami 在 cmets 中提到的,EHeapException 有一个内部 AllowFree 标志,默认情况下为 False,防止异常处理程序释放 EHeapException 的实例。

EOutOfResources 派生自 EOutOfMemory,而 EOutOfMemory 又派生自 EHeapException

SysUtils 单元有 2 个类型为 EOutOfMemoryEInvalidPointer 的单例对象。每当 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 单元会针对它的一些 GDI 错误引发 EOutOfResources,这与堆无关。

  • EOutOfResources 将其 AllowFree 标志设置为 False,而应改为 True。标志是private,所以除了SysUtils 单元外,它不能被覆盖,它只在最终确定期间对2 个单例进行覆盖。所以基本上,所有EHeapException派生的异常都会在运行时泄露。

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

此泄漏问题自 Delphi 5 以来就存在,并且已向 Embarcadero 报告:

RSP-17193: EOutOfResources memory leak

RSP-19737: EOutOfResources exception causes memory leak

【讨论】:

    猜你喜欢
    • 2011-10-25
    • 2012-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多