访问冲突 - 我该如何追查原因?

Access Violation - how do I track down the cause?

我在关闭应用程序中的表单时遇到访问冲突。它似乎只有在我访问数据库几次后才会发生,但这似乎没有意义。

我已经追踪并将 outputdebugstring 消息放入所有相关的 OnDestroy() 方法中,但 AV 似乎在我的代码之外。

这是消息的正文:

Access violation at address 00405F7C in module 'MySoopaApplication.exe'. Read of address 00000008.

如何找到 00405F7C 在应用程序中的位置?

Delphi 10.1 Berlin 中有哪些工具可以帮助我解决这个问题?

编辑:添加了更多信息...单击 "Break" 时,IDE 总是将我带到 GETMEM.INC 中的这段代码:

@SmallPoolWasFull:
  {Insert this as the first partially free pool for the block size}
  mov ecx, TSmallBlockType[ebx].NextPartiallyFreePool

进一步编辑:好吧,我找到了罪魁祸首,但我不能诚实地说调试工具让我走到了那里 - 它们似乎只是表明它不在我的代码中。

我曾使用来自网络的代码来查找 Windows 登录用户 - 就是这样:

function GetThisComputerName: string;
var
  CompName: PChar;
  maxlen: cardinal;
begin
  maxlen := MAX_COMPUTERNAME_LENGTH +1;
  GetMem(CompName, maxlen);
  try
    GetComputerName(CompName, maxlen);
    Result := CompName;
  finally
    FreeMem(CompName);
  end;
end;

一旦我用一个简单的结果替换了代码:='12345',AV 就停止了。我没有将其更改为此代码:

function GetThisComputerName: string;
var
  nSize: DWord;
  CompName: PChar;
begin
  nSize := 1024;
  GetMem(CompName, nSize);
  try
    GetComputerName(CompName, nSize);
    Result := CompName;
  finally
    FreeMem(CompName);
  end;
end;

这似乎有效,而且不会导致 AV。

感谢您的帮助,非常感谢。

在 IDE 中的 Tools|Options 下转到 Embarcadero Debuggers | Language Exceptions 并确保选中 Notify on Language Exceptions。同样在 Project Options | Compiling 下,确保选中 Debugging | Use debug DCUs

允许异常发生,然后转到View | Debug Windows | Call stack,您应该能够准确地看到异常发生的位置。它发生在数据库访问之后的事实可能是因为这会导致创建一些对象,当它被销毁时会生成 AV。可能是因为它被 Free() 编辑了两次。

如果这不能解决问题,您可能需要一个异常记录工具,例如 DavidH 提到的 madExcept。

Read of address 00000008.

这个地址是一个小数这一事实表明它是一个对象成员的地址(因为它们通常与对象的基地址相距很低)。

How do I find where in the application 00405F7C is?

使用您的应用 运行,在 IDE 中转到 Search | Go to Address。如果异常出现在您的应用程序中而不是出现在它正在使用的某个相关模块(如 .DLL)中,这应该会找到它。一旦应用程序处于 IDE 中的 运行 并在断点处停止,就会启用该菜单项。还有一个编译器命令行开关,可以通过地址查找错误。

其他人已经解释了如何诊断 AV。

关于代码本身,它存在问题:

  1. 最重要的是,您没有为缓冲区分配足够的内存。 GetMem() 对字节进行操作,但 GetComputetName() 对字符进行操作,在这种情况下 SizeOf (Char) 是 2 个字节。所以你实际上分配了你报告给 GetComputerName() 的字节数的一半,所以如果它写的比你分配的多,那么它会破坏堆内存。当您过度分配缓冲区时,损坏就消失了。所以分配时要考虑SizeOf(Char)

    function GetThisComputerName: string;
    var
      CompName: PChar;
      maxlen: cardinal;
    begin
      maxlen := MAX_COMPUTERNAME_LENGTH +1;
      GetMem(CompName, maxlen * SizeOf(Char)); // <-- here
      try
        GetComputerName(CompName, maxlen);
        Result := CompName;
      finally
        FreeMem(CompName);
      end;
    end;
    

除此之外:

  1. 您忽略了来自 GetComputerName() 的错误,因此您不能保证 CompName 甚至可以有效地传递给 Result

  2. 您应该使用 SetString(Result, CompName, nSize) 而不是 Result := CompName,因为 GetComputerName() 输出实际的 CompName 长度。当您已经知道长度时,无需浪费处理时间让 RTL 计算要复制的长度。并且由于您不检查错误,因此如果 GetComputerName() 失败,您不能依赖 CompName 被 null 终止。

  3. 您应该完全摆脱 GetMem(),而只在堆栈上使用静态数组:

    function GetThisComputerName: string;
    var
      CompName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
      nSize: DWORD;
    begin
      nSize := Length(CompName);
      if GetComputerName(CompName, nSize) then
        SetString(Result, CompName, nSize)
      else
        Result := '';
    end;