【问题标题】:ZDecompressStream() causes memory leakZDecompressStream() 导致内存泄漏
【发布时间】:2013-11-20 02:26:47
【问题描述】:

我一直在使用ZLib 函数来压缩/解压缩内存中的流。如果我尝试解压缩无效流,它会泄漏内存。以下代码会泄漏内存:

uses
  Winapi.Windows, System.Classes, System.ZLib;

function DecompressStream(const AStream: TMemoryStream): Boolean;
var
  ostream: TMemoryStream;
begin
  ostream := TMemoryStream.Create;
  try
    AStream.Position := 0;

    // ISSUE: Memory leak happening here
    try
      ZDecompressStream(AStream, ostream);
    except
      Exit(FALSE);
    end;

    AStream.Clear;
    ostream.Position := 0;
    AStream.CopyFrom(ostream, ostream.Size);
    result := TRUE;
  finally
    ostream.Free;
  end;
end;

var
  s: TMemoryStream;

begin
  ReportMemoryLeaksOnShutdown := TRUE;

  s := TMemoryStream.Create;
  try
    DecompressStream(s);
  finally
    s.Free;
  end;
end.

我尝试在这里解压缩空的TMemoryStream,并在执行结束时显示发生了内存泄漏。在 Delphi XE2 上进行测试。

关于如何防止这种泄漏发生的任何想法,因为在现实世界中,我的应用程序可能会尝试解压缩无效流并在那里泄漏内存。

QC:http://qc.embarcadero.com/wc/qcmain.aspx?d=120329 - 声称从 XE6 开始已修复

【问题讨论】:

  • 一个不错的 SSCCE,但您也应该发布泄漏消息。
  • Mayeb 您可以使用“标记和扫描”方法吗?解压前拦截GetMem/FreeMem/ReallocMem,跟踪所有分配的资源。然后在解压尝试后将它们解钩并释放所有分配的内存。
  • @LievenKeersmaekers 这是通用泄漏消息 - “发生了意外的内存泄漏。意外泄漏的中型和大型块的大小为:7212”
  • @MarkoPaunovic - 如果我没记错的话,您应该能够看到泄漏的对象/字符串/...以及分配的位置。这应该给你一些关于你是否应该关注它或者它是否不是问题的指示。我无法测试这个(没有 Delphi) 和完整的消息可能有帮助。
  • @LievenKeersmaekers 仅当他将下载并安装完整的 FastMM4 (fastmm.sf.net) 发行版并设置其调试选项以获得最详细的报告。

标签: delphi compression delphi-xe2 zlib


【解决方案1】:

这是 Delphi RTL 代码中的一个错误。 ZDecompressStream 的实现引发异常,然后无法执行整理。我们来看代码:

procedure ZDecompressStream(inStream, outStream: TStream);
const
  bufferSize = 32768;
var
  zstream: TZStreamRec;
  zresult: Integer;
  inBuffer: TBytes;
  outBuffer: TBytes;
  inSize: Integer;
  outSize: Integer;
begin
  SetLength(inBuffer, BufferSize);
  SetLength(outBuffer, BufferSize);
  FillChar(zstream, SizeOf(TZStreamRec), 0);

  ZCompressCheck(InflateInit(zstream));   <--- performs heap allocation

  inSize := inStream.Read(inBuffer, bufferSize);

  while inSize > 0 do
  begin
    zstream.next_in := @inBuffer[0];
    zstream.avail_in := inSize;

    repeat
      zstream.next_out := @outBuffer[0];
      zstream.avail_out := bufferSize;

      ZCompressCheck(inflate(zstream, Z_NO_FLUSH));

      // outSize := zstream.next_out - outBuffer;
      outSize := bufferSize - zstream.avail_out;

      outStream.Write(outBuffer, outSize);
    until (zstream.avail_in = 0) and (zstream.avail_out > 0);

    inSize := inStream.Read(inBuffer, bufferSize);
  end;

  repeat
    zstream.next_out := @outBuffer[0];
    zstream.avail_out := bufferSize;

    zresult := ZCompressCheck(inflate(zstream, Z_FINISH));

    // outSize := zstream.next_out - outBuffer;
    outSize := bufferSize - zstream.avail_out;

    outStream.Write(outBuffer, outSize);
  until (zresult = Z_STREAM_END) and (zstream.avail_out > 0);

  ZCompressCheck(inflateEnd(zstream));   <--- tidy up, frees heap allocation
end;

我从我的 XE3 中获取了这个,但我相信它在所有版本中基本相同。我已经强调了这个问题。对inflateInit 的调用从堆中分配内存。它需要与对inflateEnd 的呼叫配对。因为ZCompressCheck 在遇到错误时会引发异常,所以对inflateEnd 的调用永远不会发生。因此代码泄漏了。

在该单元中对inflateInitinflateEnd 的其他调用被try/finally 正确保护。只是在这个函数中的使用似乎是错误的。

我的建议是您将Zlib 单元替换为正确实施的版本。

【讨论】:

  • 大卫,QC 已发布,请参阅问题的编辑 :-)
  • @Arioch'The 谢谢你。对您花时间提交它有好处。 FWIW,我没有打开cmets,有很多。我只是查看了代码并立即发现了缺陷。直到后来,我才阅读了 cmets。无论如何,最好问题有答案,而不是把细节埋在 cmets 中。
  • 好吧,我认为给 topicstarter 几天时间会更公平,所以他会发布自己的答案并接受它。但他同意在你的答案上盖章——这也没关系。
  • @Arioch'The 通常他们会继续前进,因为他们的问题得到了解决。并留给我们整理问题。正如我所说,我没有读过cmets。我刚刚回答了一个似乎需要回答的问题。
猜你喜欢
  • 2015-07-06
  • 2014-06-07
  • 2011-10-28
  • 2016-01-18
  • 2012-12-13
  • 1970-01-01
  • 2011-01-08
  • 2011-02-01
  • 2016-01-07
相关资源
最近更新 更多