代码之家  ›  专栏  ›  技术社区  ›  Edijs Kolesnikovičs

Delphi调试器-发生异常时转到行

  •  3
  • Edijs Kolesnikovičs  · 技术社区  · 10 年前

    我在工作中切换到其他项目,我注意到DelphiXE2调试器没有显示引发异常的行。回到家后,我开始调查。然后我发现它可以在 工具->选项->调试器 选项和检查 集成调试 。我还检查了下面的所有内容 语言异常 在里面 要忽略的异常类型 列表 语言异常时通知 左勾选。 项目->选项->正在编译 ,我有默认值 溢流 范围检查 启用。我在跑步 调试 建筑我 清洁

    我以前没有注意到,但现在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;
    

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

    非常感谢。

    1 回复  |  直到 10 年前
        1
  •  3
  •   Johan    10 年前

    让我先回答这个问题。

    如何使编译器尽可能多地显示

    编译器向您显示错误在对btnclick的调用中。
    技巧是用 第5页 .
    然后重新生成(!)应用程序并再次运行。
    执行将停止该断点。
    使用 第8页 直到错误出现。
    放置断点 第5页 在生成错误的行上。
    中止并重新运行应用程序。
    当您到达第二个断点而不是按 第8页 第7页 要进入导致错误的例程,请继续按 第7页 / 第8页 直到你明白问题的真正原因。

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

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

    本规范(附件A):

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

    与本规范完全没有共同之处(附件B):

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

    附件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;
    

    注意 default 关键字 Items 所有物

    这意味着当你打电话 _List[0] ,你真的在打电话 _List.Items[0] ,转换为 Caption:= _List.Getitems(0) ,因为属性上的read修饰符。
    这个 违约 关键字允许您省略 .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没有生成错误的确切行的调试信息,所以它会做下一件最好的事情,并尝试进行猜测。

    短版本
    stringlist是一个复杂的抽象,假装是一个数组。
    调用了大量代码,使得编译器很难准确定位错误。

    在附件B中:

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

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

    短版本
    这个 平原 数组是一个基本的构建块,没有对其他地方代码的隐藏调用,使得编译器很容易发现错误。

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