【问题标题】:Creating or forcing an error in Delphi在 Delphi 中创建或强制错误
【发布时间】:2013-04-14 07:50:37
【问题描述】:

对于我的一些程序和函数,我已经对参数实施了各种检查,以便在参数以某种方式超出范围时强制停止执行。 我发现最好在我自己的代码中检查这一点,而不是由于内存写入错误而导致异常崩溃。

考虑一下简单的代码:

PROCEDURE Test(OneDigitNumbers:BYTE);
BEGIN
  IF OneDigitNumbers>9 THEN ProduceErrorMessage;
END;

begin
  Test( 1);
  Test( 2);
  Test( 9);
  Test(12);
end.

我在实际产生错误消息方面没有问题,我唯一的“问题”是 Delphi 中的调试器总是指向创建异常的过程。 是否有创建此异常或错误消息的方法,以便调试器指向参数超出范围的行? 在我的例子中,它应该指向:

Test(12);

也许会说“参数超出范围。有效范围是 0-9。传递的参数是:12”

即使是说这是不可能的答案也会很有用(如果您确定这是不可能的),因为那样我就会忘记这一点并制定另一种调试方法。

【问题讨论】:

  • 当在调试器下引发异常时,您可以查看调用堆栈,根据您的示例,调用堆栈将具有第一项 ProduceErrorMessage(意味着这是引发异常的位置),第二项是 Test(调用 ProduceErrorMessage 过程的过程),第三项是调用 Test 的函数/过程/方法。
  • @ComputerSaysNo - 谢谢你的提示 :) 我不知道这一点。也许可以在引发异常之前使用调用堆栈?只是让它倒退两到三步?
  • 不要弄乱调用堆栈。它会给你带来比现在更多的麻烦。或许检查一下。并使用您在异常消息中找到的内容。 Exception 类确实提供了对较新版本中的调用堆栈的访问,但它要求已经创建了异常,甚至可能引发了异常。在后一种情况下,您必须引发异常并在函数中捕获它,以便您可以修改消息,然后重新引发它以传播到调用代码。有点乱,但可以做到。
  • 我正要发表一篇阐述。
  • 一个更具理论性的问题。您在这里测试先决条件,并且该测试应该在您的例程外部进行,而不是在内部进行。这与直觉相反,但这就是您编写干净代码的方式。如果调用者问了一些没有意义的问题,你不能指望被调用者处理他的情况。为了安全起见,您可以在例程中添加断言,但您可能永远不会依赖例程之外的断言。否则,您可能会失去正式接受其他类型输入的先决条件。

标签: delphi


【解决方案1】:

要按要求回答问题,您可以将测试函数内联:

procedure Test(OneDigitNumbers: byte); inline;

然后编译器会将 Test 的代码写入每个调用函数。虽然你可以这样做,但我的建议是你不这样做。这只是一个技巧,但我认为它对你没有帮助。

如果要在返回地址引发异常,可以这样做:

raise Exception.CreateFmt(
  'Exception blah blah at %p.',
  [ReturnAddress]
) at ReturnAddress;

如果您想在堆栈中走得更远,那么您必须使用CaptureStackBackTrace 之类的东西。将回溯跟踪与 raise at 结合起来,您可以在调用堆栈中的任何位置引发异常,如果您真的认为这是个好主意。我认为这不是一个好主意,如下所述。

如果您使用良好的调试工具,例如 madExcept,那么 madExcept 错误报告中的调用堆栈会告诉您发生错误时需要知道的所有信息。


随着 cmets 中的额外说明,您似乎真正想要发生的是异常包含来自调用堆栈更高层的信息。在我看来,要求被调用者报告有关其调用者的信息是违反封装的。所以如果你想包含来自调用者的信息,让调用者捕获异常,添加信息,然后重新引发。

【讨论】:

  • 太棒了 :) 这回答了我另一个从未想过的关于内联的问题。绝对最有用。但是,我不能使用这种内联方法进行超出范围检查。我担心我的程序会超出比例。也许可以在调用异常之前读取堆栈跟踪。?我一直都知道始发呼叫向后退了 2 步。也许可以在引发异常之前强制堆栈跟踪向后退 2 步?
  • 害怕您的程序增长超出比例?为什么会害怕。害怕什么?会发生什么?如果要范围检查,启用范围检查?你知道怎么做吗?
  • :) 并不是我害怕 100 mb 的程序。这在今天的计算机上并不重要。我只是更喜欢让我的程序又好又小。当我测试我的软件时,我总是在激活范围检查的情况下运行。为了以后的速度,当一切正常时,我将其停用。但这不仅仅是关于范围检查。它是产生设计错误消息的能力,在引发异常时指向它源自的特定代码行。范围检查仍然只能说明实际问题出在哪里,而不是问题的原因。
  • 把 madExcept 放到你的程序中。让它为你施展魔法。它有你需要的一切。
  • 我的意思是进行我自己的范围检查,以便生成设计的错误消息,以查明问题所在。在编译器中启用范围检查没有任何作用:)这个答案非常接近我所追求的。也许如果我在我的过程中使用 Inline 来创建设计的错误消息,然后将异常指向 ReturnAddress 处的 [ReturnAddress]);非常感谢您提供此信息 :) 也感谢您提供有关 madExcept 的提示 :)
【解决方案2】:

您正在寻找subrange type

type
  TOneDigitNumber = 0..9;

procedure Test(OneDigitNumbers: TOneDigitNumber);
begin
  // Do something
end;

begin
  Test( 1);
  Test( 2);
  Test( 9);
  Test(12);   // compiler error '[DCC Error] MyStuffTest.pas(33): E1012 Constant expression violates subrange bounds
end.

【讨论】:

  • rangesetting 是一个很好的技巧,但是它是不可能使用的,因为可能每次调用过程的范围都不同。我所做的一切(几乎)都是基于动态结构的,动态数组会根据对空间的需要而增长或缩小。如果设置 X:=35; 则 Delphi 中的范围检查将不起作用;然后做测试(X);甚至作为循环: For X:=0 to 35 DO Test(X);
  • 无法检查动态数组的范围,因为它们是动态的。编译器无法检查超出范围值的范围,因为它们是动态的(可以在运行时更改)。您的问题没有提及数组或任何此类信息;您的 Test 过程接受单个 Byte 值。如果您现在有其他问题,请照此发布。如果您的整个问题不同,则应删除此问题并发布该问题。事后你不能改变问题的定义。 :-)
  • 是的,您可以对文字执行此操作,但这并不是非常有用
  • @KenWhite :重点不是动态数组或编译器来检查错误。关键是要创建一个异常,让 Delphi 调试器显示原始代码行。错误源自的代码行。查找错误不是编译器的工作,因为它永远无法在我的程序上完成。我可以自己捕获超出范围参数的错误。创建异常时,我只是无法将调试器直接指向原点。我希望制作一个像“RaiseExceptionAt_ProcedureCaller”这样的程序
【解决方案3】:

我对您的问题的评论有点详细说明

type
  EMyOwnRangeError = class(ERangeError)
    // You can also add your own member variables for easier inspection
  public
    constructor CreateFrom(const aRangeError: ERangeError);
  end;

constructor EMyOwnRangeError.CreateFrom(const aRangeError: ERangeError);
begin
  // Do whatever you need to inspect the call stack in aRangeError 
  // and modify the message and/or set any extra member variable that you
  // you define on EMyOwnRangeError.
  // No help from me on this, quite simply because I don't have Delphi 
  // installed on the machine I am currently working at.
end;

procedure MySpecialTest(const aWhatever: Byte);
begin
  try
    if (aWhatever < 0) or (aWhatever > SOMEUPPERRANGE) then
      raise ERangeError.Create;

    // Normal code for MySpecialTest

  except
    on E: ERangeError do raise EMyOwnRangeError.CreateFrom(E);
    else
      raise; // Make sure other exceptions are propagated.
  end;
end;

【讨论】:

    【解决方案4】:

    我现在已经基本上测试了我从 David Heffernan 那里得到的想法中的方法。 我只是在我的一个可重用单元中添加了这个简单的代码:

    PROCEDURE TestError(Par:BYTE);
      BEGIN
        TRY
        FINALLY
          IF Par>9 THEN Raise Exception.CreateFmt('Error Blah blah blah at ',[Par]) AT @Par;
        END;
      END;
    

    当使用高于 9 的参数调用此过程时,它会强制异常。 Delphi 询问“Break or Continue”,然后我点击 Break。

    结果几乎是我想要的,但它是如此接近以至于我可以接受。 调试器在调用过程之后的行上弹出一条漂亮的红线。

    我也尝试不使用 TRY-Finally-End,然后它完全错误,实际上从调用堆栈中显示了另一个级别的红线..

    无论如何。我觉得这个结果比我以前的结果要好得多。现在我的调试将是一种快乐而不是痛苦。 谢谢你:)

    【讨论】:

    • 你为什么要在@par 加注?这是你函数中的一个局部变量。
    • 我在 ReturnAddress 处没有收到您的“at %p.', [ReturnAddress]);”去工作。无法识别 %p,也无法识别 ReturnAddress。所以我只是测试了一下。这是合乎逻辑的,因为 Par 是对我可见的调用者的唯一链接。我不知道如何找到退货地址。
    • 您使用的是旧版 Delphi 吗?您在堆栈上找到返回地址。它被调用指令推送到那里。
    • 我使用的是CodeGear Delphi 2009。至于“在堆栈上查找返回地址”,我不知道该怎么做:-(
    • 是的,ReturnAddress 从那时起发生了变化。这里有一个可能有帮助的问题:stackoverflow.com/questions/8950513/… 还可以看看 D2009 中SysUtils.Abort 的实现。这向您展示了如何从堆栈中弹出返回地址。如果您想要更深入的回溯,请使用CaptureStackBackTrace
    猜你喜欢
    • 2013-10-15
    • 2017-09-25
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    • 2014-06-14
    • 2020-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多