【问题标题】:Infinite Loop in Delphi ProcedureDelphi程序中的无限循环
【发布时间】:2015-11-10 22:50:31
【问题描述】:

我在使用 Delphi 的 TMemoryStream(或 TFileStream)时遇到了一个奇怪的问题。在将流的一部分读入字节数组时。下面是一些代码作为示例。

procedure readfromstream();
var
     ms : TMemoryStream;
     buffer : array of byte;
     recordSize : Integer;
begin
  try
  begin
     ms := TMemeoryStream.Create();
     ms.LoadFromFile(<some_path_to_a_binary_file>);

     while ms.Position < ms.Size do
     begin
         buffer := nil;
         SetLength(buffer, 4);
         ms.ReadBuffer(buffer, 4);
         move(buffer[0], recordSize, 4);

         SetLength(buffer, recordSize);
         ms.Position := ms.Position - 4;           // Because I was having issues trying to read the rest of the record into a specific point in the buffer
         FillChar(buffer, recordSize, ' ');
         ms.ReadBuffer(buffer, recordSize);        // Issue line ???

         // Create the record from the buffer
     end;
  finally
  begin
     ms.Free();
  end;
end;

过程被称为,

// Some stuff happens before it

readfromstream();

// Some stuff happens after it

在调试时,我可以看到它将流读入缓冲区,并且记录适当地存储在内存中。然后程序正常退出,调试器退出程序,但我最终直接回到程序并重复。

通过强制程序过早退出,我认为问题涉及ms.ReadBuffer(buffer, recordSize);,但我不明白为什么会导致问题。

这个过程只被调用一次。我的测试数据只有一个条目/数据。 任何帮助将不胜感激。

【问题讨论】:

  • 欢迎来到 SO。你分析调用堆栈了吗?该程序必须由某人或某事调用。你的程序对事件有反应吗?
  • 谢谢。我忘了提到,是的,我做到了,它只在调用堆栈中出现一次,并且来自它上面的函数。我可能错过了一些东西,关于如何仔细检查的任何提示。
  • FWIW,这不是您的真实代码(没有 TMemeoryStream 类型,AFAICT)。所以可能有我们看不到的问题,因为它们可能在手动复制过程中丢失了,或者我们在这里看到了原始代码中没有的问题,出于同样的原因。请使用复制和粘贴在此处复制您的代码。不要重新输入。
  • 正如@David 所说,您正在复制指针和后续字节。由于这是在堆栈上,并且堆栈向下增长,这意味着您正在覆盖堆栈的相当一部分(413 字节?)。反过来,这意味着几个局部变量、堆栈上的返回地址以及任何任何参数以及可能大量的调用堆栈帧、返回地址和参数都被覆盖。那种严重的堆栈损坏会导致各种可怕的问题,而无限循环只是其中之一。
  • @RudyVelthuis 感谢您的解释,这正是在大卫说我导致内存损坏后我所想的。不幸的是,我仍然无法按照 David 解释的方式在 recordSize 中读取它,所以稍后我将不得不再看一遍

标签: delphi delphi-xe8


【解决方案1】:

对不起,我无法添加评论,因为我是新手 :) 此回复基于我对 Clayton 代码的理解,并根据他对 recordSize 值的评论。

David 的代码循环的原因可能是您将每个四字节“块”解释为一个数字。我会假设您的第一个 Stream.Readbuffer 是正确的,并且文件中的前四个字节是一个长度。

现在,除非我弄错了,否则我预计 recordSize 通常会大于 SizeOf(recordSize),我认为应该是 4(int 的大小)。然而,这条线在这里毫无意义。

根据我之前的假设,SetLength 是正确的。

现在,您的 Move 是故事遇到障碍的地方:自从您阅读了篇幅后,您还没有阅读任何内容!所以在搬家之前,你应该有: bytesRead := Stream.Readbuffer(Pointer(buffer)^, recordSize);

现在您可以检查 EOF: 如果 bytesRead recordSize 然后 提高...;

...并将缓冲区移动到某处(如果您愿意): 移动(缓冲区[0],目的地[0],记录大小);

并且您可以读取下一个 recordSize 值。

重复直到 EOF。

【讨论】:

  • 刚刚意识到:我说的那句话毫无意义,不是吗!这是不正确的:Read 应该将读取的字节返回到 bytesRead 并且 this 应该与 SizeOf(recordSize) 进行比较。
  • 循环的原因是他正在读取几百字节(他说:413字节)到堆栈上,覆盖本地变量缓冲区,可能还有本地堆栈帧,返回地址等. 他不会写入缓冲区指向的内存,而是写入称为缓冲区本身的指针,以及更多的堆栈。
  • @Pat 这都是错误的。你的分析是错误的。您显示的代码无法编译。 ReadBuffer 是一个过程。如果它没有读取请求的字节数,它会引发。问题和我的回答完全一样。
  • 强度!为什么我把它读作“Stream.Read”?放弃口香糖的糟糕一周。感谢您的回复!
  • @David 我认为克莱顿在您的代码中看到的问题是您假设长度字段是记录的一部分。当第二个 ReadBuffer 读取 recordSize 字节而不是 (recordSize - SizeOf(recordSize)) 字节时,您的代码可以正常工作。
【解决方案2】:
FillChar(buffer, recordSize, ' ');

在这里,您正在覆盖动态数组变量,一个指针,而不是写入数组的内容。这会导致内存损坏。那时几乎所有事情都会发生。

无论如何,拨打FillChar 是不必要的。无论如何,您都将读入整个数组。删除对FillChar 的调用。

为了将来参考,为了正确调用,你可以这样写:

FillChar(Pointer(buffer)^, ...);

FillChar(buffer[0], ...);

我更喜欢前者,因为后者在数组长度为零时会出现范围错误。

然后

ms.ReadBuffer(buffer, recordSize);

犯了完全相同的错误,写入数组变量而不是数组,从而破坏了内存。

应该是这样的

ms.ReadBuffer(Pointer(buffer)^, recordSize);

ms.ReadBuffer(buffer[0], recordSize);

循环中的前 4 行很笨拙。直接读入变量:

ms.ReadBuffer(recordSize, SizeOf(recordSize));

我建议您对您读取的recordSize 的值执行一些完整性检查。例如,任何小于4 的值显然是一个错误。

将流指针移回并再次读取没有什么意义。您可以将recordSize 复制到第一个4 字节和数组中,然后读取其余部分。

Move(recordSize, buffer[0], SizeOf(recordSize));
ms.ReadBuffer(buffer[SizeOf(recordSize)], recordSize - SizeOf(recordSize));

内存流似乎也很浪费。为什么要将整个文件读入内存?这会给大文件的地址空间带来压力。使用buffered file stream

让调用者分配流会给调用者更多的灵活性。然后,它们可以从任何类型的流中读取,而不受限于使用磁盘文件。

您的try/finally 块是错误的。您必须在try 之前立即获取资源。正如您所拥有的,构造函数中的异常会导致您在未初始化的变量上调用Free

更好的版本可能是:

procedure ReadFromStream(Stream: TStream);
var
  buffer: TArray<byte>;
  recordSize: Integer;
begin
  while Stream.Position < Stream.Size do
  begin
    Stream.ReadBuffer(recordSize, SizeOf(recordSize));     
    if recordSize < SizeOf(recordSize) then
      raise ...;

    SetLength(buffer, recordSize);
    Move(recordSize, buffer[0], SizeOf(recordSize));
    if recordSize > SizeOf(recordSize) then
      Stream.ReadBuffer(buffer[SizeOf(recordSize)],
        recordSize - SizeOf(recordSize));

    // process record
  end;
end;       

【讨论】:

  • 感谢您提供的信息,我会牢记这一点。然而遗憾的是,这并没有解决我的问题,程序仍然被调用,仍然正确地做所有事情,然后由于某种原因最终回到了程序的顶部
  • 您有两个内存损坏。刷新页面,再次阅读答案。
  • 感谢@David 的帮助。我确定您提供给我的内容是正确的,我只有一个问题。当recordSize被写入文件时,在这种情况下为413,当我之前读它时,很长一段时间,它读为413。当我使用Stream.ReadBuffer(recordSize, SizeOf(recordSize));将它直接读入变量时,它最终是数量超过 1900 万。任何想法为什么会这样?
  • 您的新代码中可能还有其他错误。
  • 所以我终于有时间回去看看我做错了什么。当我将记录写入文件时,我使用的是TFileStream.Write,因此通过将其更改为使用TFileStream.WriteBuffer,一切正常。感谢@David 的帮助
猜你喜欢
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-16
相关资源
最近更新 更多