【问题标题】:Delphi debugger - go to line when exception happensDelphi 调试器 - 发生异常时转至行
【发布时间】:2014-09-02 19:16:33
【问题描述】:

我在工作中切换到其他项目,我注意到 Delphi XE2 调试器没有显示引发异常的行。当我回到家时,我开始调查。然后我发现可以在Tools -> Options -> Debugger选项中禁用它并检查Integrated debugging。此外,我在 要忽略的异常类型 列表中取消选中 Language exceptions 下的所有内容。 通知语言异常保持选中状态。 项目 -> 选项 -> 编译,我在那里有默认设置并启用了 溢出范围检查。我正在运行 Debug 构建。我清理了它。

我以前没有注意到,但现在 Delphi 调试器在我调用此代码时不给我该行:

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  _List: TStringList;
begin
  _List := TStringList.Create;
  try
    Caption := _List[0]; // 'List index out of bounds (0)' here
  finally
    FreeAndNil(_List);
  end;
end;

但这有效(仅用于显示调试器确实显示了某些内容的行):

{$R+} // Range check is ON
procedure TForm1.BitBtn2Click(Sender: TObject);
var
  _myArray: array [1 .. 5] of string;
  i: integer;
begin
  for i := 0 to 5 do
  begin
    _myArray[i] := 'Element ' + IntToStr(i); // Range check error here
    ShowMessage('myArray[' + IntToStr(i) + '] = ' + _myArray[i]);
  end;
end;

这里发生了什么?如何让调试器尽可能多地显示?

谢谢。

【问题讨论】:

  • 您是否尝试过在项目选项中启用use debug dcu's(仅在您实际拥有这些 dcu 时才有效)。

标签: delphi debugging exception line delphi-xe2


【解决方案1】:

让我先回答这个问题。

如何让编译器尽可能的显示

编译器显示错误在于对 btnclick 的调用。
诀窍是使用 F5 在 btnclick proc 的第一行放置一个断点。
然后重建 (!) 应用程序并再次运行。
执行将停止该断点。
使用 F8 单步执行代码,直到出现错误。
在产生错误的行上放置一个断点 F5
中止并重新运行应用程序。
当你到达第二个断点而不是按 F8 时,按 F7 进入导致错误的例程,继续按 F7/ F8 直到您看到究竟是什么导致了问题。

为什么会这样?
编译器通过跟踪堆栈跟踪来追溯异常的来源。
因为在您的情况下,生成异常的代码没有堆栈跟踪(因为它不是调试代码),所以编译器无法执行此技巧,而是遵循 does 具有的堆栈跟踪;它在您的代码中上移一级并在那里标记异常。

详细了解一下
你在比较苹果和橘子。

此代码(附件 A):

Caption := _List[0]; // 'List index out of bounds (0)' here

与此代码完全没有共同之处(图表 B):

_myArray: array [1 .. 5] of string;
....
_myArray[0]:= 'Hallo';

Exhibit A 使用了 TStringList 类的 items 属性,其定义或多或少如下(我已经简化了一点,但基本原理是正确的)

type
  TStringList = class(TStrings)
  strict private
    FList: array of string;
  ....
  private
    procedure Put(index: integer; const value: string);
    function Get(index: integer): string;
  published
    property Items[index: integer]: string read Get write Put; default;
    //   ------------------------------------------------------^^^^^^^
    ....
  end;

注意Items 属性末尾的default 关键字。

这一切的意思是,当您调用 _List[0] 时,您实际上是在调用 _List.Items[0],由于属性上的 read 修饰符,它被转换为 Caption:= _List.Getitems(0)
default 关键字允许您省略 .Items

Get 代码如下所示:

function TStringList.Get(Index: Integer): string;
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);  <<-Here is the line that generates the error*
  Result := FList[Index].FString;
end;

*其实这个错误是在Error这个例程里面产生的

除非您有 RTL/VCL 源代码并且您正在使用调试 DCU 运行,否则您不会在异常的确切触发器(位于 system.classes 内部)单元上获得中断.
请注意,此错误不依赖于范围检查,它总是会触发。
因为 Delphi 没有生成错误的确切行的调试信息,所以它会做次优的事情并尝试进行猜测。

短版
字符串列表是一个伪装成数组的复杂抽象。
大量代码被调用,使得编译器难以准确定位错误。

在图表 B 中:

_myArray: array [1 .. 5] of string;
....
i:= 0;   
_myArray[i]:= 'Hallo';

生成范围检查错误或访问冲突。
这两个错误都发生在确切的行上,允许编译器在正确的位置停止。

短版
plain 数组是一个基本的构建块,没有对其他地方的代码的隐藏调用,使得编译器非常容易查明错误。

了解属性
类和记录属性(以及现在的类运算符)看起来像是对变量的简单赋值/操作,但实际上是对(可能是复杂的)子例程的调用。

【讨论】:

  • 我知道我提供的两个代码没有任何共同之处。我提供它只是为了表明调试器只为“列表索引超出范围”异常提供了任何代码行。是的,断点有帮助,但是当有无数行代码时,事情就会变得复杂。我只是想知道为什么有时调试器会给我导致问题的行,而有时却没有。
  • 这就是我告诉你的,你的代码中没有产生异常,所以调试器不会在你的代码中停止。
猜你喜欢
  • 2015-07-07
  • 1970-01-01
  • 2017-01-23
  • 2014-12-29
  • 2010-09-14
  • 2014-08-11
  • 2016-01-14
  • 1970-01-01
  • 2019-02-21
相关资源
最近更新 更多