下面是一些代码的压缩版本,这些代码会导致一个范围检查错误和一个溢出错误,我应该打开这些编译器检查指令吗,我理解为什么这会导致一个溢出,在C1的乘法上,它似乎超过了数据类型的最大值。但是为什么这也会触发一个范围检查错误呢? Delphi 的文档和其他关于堆栈溢出的文章使它听起来像范围-检查错误通常是因为数组访问超出了界限,但是我没有访问一个数组,它说这行导致了范围检查错误,也许是因为param 1的赋值,但是如果是这样,为什么这是一个范围检查而不是溢出错误呢?
const
C1 = 44001;
C2 = 17999;
function fxnName(..other params...; param1: Word): String;
var
someByte: byte;
begin
// some code
// by now we're in a loop. the following line is where it breaks to in the debugger:
param1 := (someByte + param1) * C1 + C2;
// more code
end;
如果它是相关的,那么当它在调试器中的该行中断时,所有的值看起来都和预期的一样,除了param 1,它显示“未声明的标识符:'param 1'“当我让 Delphi 评估它时。
2条答案
按热度按时间smdncfj31#
有关范围检查的文档指出:
$R指令启用或禁用范围检查代码的生成。在{$R +}状态下,所有数组和字符串索引表达式都被验证为在定义的界限内,并且对标量和子范围变量的所有赋值都被检查为在范围内。如果范围检查失败,则引发ERangeError异常(如果未启用异常处理,则终止程序)。
所以这里的原因是给标量赋值,它被传递了一个超过上限的值。
有关简单类型和子范围类型的范围检查错误,请参见docwiki Simple Types。
示例:
对所有平台无关整数类型的$R和$Q的所有组合的汇总测试:
R =距离误差;O =溢出误差;x =无
测试是在32位模式下使用XE2进行的(伪代码):
bbmckpt72#
我见过太多的Delphi程序员在编写相当大的程序时从来没有激活过范围、溢出和Assert检查。当然,如果你愿意,你可以这样做,但是你的代码会有更多的bug。
所以,如果你允许我让我插入一个平行(bug仍然相关)和你的问题,希望说服更多的程序员启用这3检查现在.一个字的警告也包括在最后.
这将检查某些整数算术运算(+、-、*、Abs、Sqr、Succ、Pred、Inc和Dec)是否溢出。例如,在+(加法)运算之后,编译器将插入附加的二进制代码,以验证运算结果是否在支持的范围内。
当对整数变量的运算产生的结果超出该变量的范围时,就会发生"整数溢出"。例如,如果将整数变量声明为16位有符号整数,则其值的范围可以是-32768到32767。如果对该变量的运算产生的结果大于32767或小于-32768,发生整数溢出。
发生整数溢出时,操作的结果是未定义的,可能导致程序中出现未定义的行为:·Wrap-around结果可能会产生一个wrap-around值,这意味着数字32768实际上会存储为1,因为它比我们能存储的最大值高1个单位(32767)。·截断结果可能会被截断或修改,以适合整数类型的范围。例如,数字32768实际上将被存储为32767,因为这是我们可以存储的最高值。
未定义的程序行为是最严重的错误之一,因为它不是一个容易重现的错误,所以很难跟踪和修复。
如果您激活此选项,需要付出很小的代价:程序的速度将稍微降低。
检查I/O操作的结果。如果I/O操作失败,则会引发异常。如果关闭此开关,则必须手动检查I/O错误。如果激活此开关,则需要付出较小的代价:程序的速度将降低,但并不显著,因为与I/O操作本身所需的毫秒级时间相比,此检查引入的几微秒根本算不上什么(硬盘驱动器很慢)。
Delphi极客称之为"最重要的Delphi设置",我完全同意。它检查所有数组和字符串索引表达式是否在定义的范围内。它还检查所有标量和子范围变量的赋值是否在范围内。
下面是一个示例代码,如果范围检查不可用,它会毁了我们的生活:
要激活运行时错误检查,请转到"项目选项"并选中以下三个框:
在"项目选项"
中启用运行时错误检查
一个好的程序员必须在代码中使用Assert来提高程序的质量和稳定性。说真的,伙计!你真的需要使用Assert。
Assert用于检查在程序的某个点上应该始终为真的条件,并在不满足条件时引发异常。在SysUtils单元中定义的Assert过程通常用于执行Assert。
你可以把Assert看作是穷人的单元测试。我强烈建议你深入研究Assert。它们非常有用,而且不需要像单元测试那样多的工作。
典型示例:
但是为了让程序检查我们的Assert,我们需要在项目设置-〉编译器选项中激活这个特性,否则它们将被忽略,就像它们不在我们的代码中一样。确保你理解我刚才所说的含义!例如,如果我们在Assert中调用有副作用的例程,我们会搞砸。在下面的例子中,在调试过程中,当Assert打开时,Test()函数将被执行,并且"This was executed"将出现在备忘录中。2然而,在发布过程中,该文本将不会出现在备忘录中,因为现在Assert被简单地忽略了。3恭喜,我们刚刚使程序在调试/发布模式下有了不同的行为。
下面是一些如何使用它的示例:
1要检查输入参数是否在0..100范围内:
2在使用指针之前检查它是否不为空:
3在继续之前检查变量是否具有特定值:
Assert也可以通过在项目选项中定义NDEBUG符号或使用{$D -}编译器指令来禁用。
在某些情况下,我们还可以使用Assert作为处理错误和异常的更优雅的方式,因为它更具可读性,而且它还包括一个自定义消息,这将帮助开发人员了解哪里出错了。
就我个人而言,我经常在例程的顶部使用它来检查参数是否有效。
激活这个特性会(自然地)使你的程序变慢,因为...嗯,有一行额外的代码要执行。
"没有什么是免费的"
一切美好的事物都是有代价的(幸运的是,我们的代价很小):启用运行时错误检查和Assert会降低程序的速度,并使程序变得更大。
现在的电脑有很多内存,所以内存的微小增加是无关紧要的,所以我们先把它放在一边,但让我们看看速度,因为这不是我们可以轻易忽视的:
正如我们所看到的,程序的速度受到这些运行时检查的强烈影响。如果我们有一个程序,速度是关键,我们最好只在调试期间激活“运行时错误检查”。我所做的,我也让它在第一个版本激活,并等待几个星期。如果没有错误报告,然后我发布了一个更新,其中“运行时错误检查”关闭。
就我个人而言,我让“IO检查”始终处于活动状态。由于这种检查而对性能的影响是微观的。
重要警告:
如果你有一个已经存在的项目写得不太好,并且你激活了下面的任何一个运行时错误检查,你的程序可能会比平常更频繁地崩溃。运行时错误检查例程并没有破坏你的程序。它总是被破坏--你只是不知道。运行时检查例程现在正在寻找所有那些代码可疑、低劣和发臭的地方。运行时检查的唯一目的是查找程序中的错误。