Delphi - 检测 Int64 溢出错误

Posted

技术标签:

【中文标题】Delphi - 检测 Int64 溢出错误【英文标题】:Delphi - Detect Int64 Overflow Error 【发布时间】:2012-07-05 12:23:09 【问题描述】:

在 Delphi 中如何检测 Int64 的溢出错误?

对于整数,我们可以这样做:

type
MyInt = Integer; //Int64

function TryMaxTimes10(out Res: MyInt): boolean;
var
  a, b: MyInt;
begin
  $Q+
  try
    a := High(MyInt);
    b := 10;
    Res := a * b; //REF1
    Result := True; 
  except
    Result := False;
  end;
  $Q-
end;

对于MyInt = Integer,REF1 行给出异常,因此TryMaxTimes10 返回false

但是如果我们将 MyInt 更改为MyInt = Int64,那么 REF1 不会给出异常并且TryMaxTimes10 返回true

我了解$Q+ 的帮助没有具体提及Int64: ... $Q+ state, certain integer arithmetic operations ... are checked for overflow

问题:所以我的问题是,我们如何检测 Int64 的溢出错误?

(我使用的是 Delphi 7。在较新版本的 Delphi 中也会发生同样的事情吗?)

【问题讨论】:

在进一步调查中,似乎 __llmulo (system.pas) 中存在错误。 我在这里找到了(某种)解决方法:qc.embarcadero.com/wc/qcmain.aspx?d=34049,我可以使用类似的 Fastcode 来修补 __llmulo。问题是解决方法代码可能包含错误(请参阅 QA 页面下方的 cmets)。有人有睾丸/工作 __llmulo? 【参考方案1】:

这是一个已知问题。见http://qc.embarcadero.com/wc/qcmain.aspx?d=10185,cmets 安迪写在底部。

我的建议是创建一个函数(我没有编译也没有测试这个 - 只是一个例子):

function Foo(A, B : Int64) : Int64;
var bNeg : boolean;
begin
  // Do we expect a negative result?
  bNeg := ((a < 0) xor (b < 0));
  // Get the real result
  Result := a * b;
  // If the result is wrong, raise an error
  if ((Result < 0) xor bNeg) then begin
    // Raise EOverFlow
  end;
end;

【讨论】:

谢谢保罗。用上面的乘法函数调用替换所有 * 是很困难的。我正在寻找一个更全局的解决方案,也许可以修补 __llmulo(调用 system.pas 函数来执行 Int64 乘法),类似于 Fastcode。 是的,那会容易得多!作为一个仅供参考,我使用的是 Delphi XE2,我仍然可以复制这个错误。当然它在 D2005 中已被修复为“关闭”......所以“升级你的 Delphi”也不是一个答案。 嗨 Paul - 出于好奇,如果 __llmulo 过程(在 system.pas 中)仍然显示(在过程上方的 cmets 中)“64 位有符号乘法,带有溢出检查( 98.05.15: 尚不支持溢出)”以及代码是否与 __llmul 过程相同? 是的,有相同的评论。这两个过程之间的代码实际上是 100% 相同的。 请注意QualityCentral has now been shut down,因此您无法再访问qc.embarcadero.com 链接。如果您需要访问旧的 QC 数据,请查看 QCScraper。【参考方案2】:

此错误已在 RAD Studio 10.2 Tokyo 中修复。 该问题可以在here 找到(但必须使用 embarcadero 帐户登录才能查看)。

这是 John O'Harrow 的 __llmulo 的正确版本(在 MPL 1.1 下获得许可)随 Delphi 10.2 及更高版本一起提供:

//  Param 1(edx:eax), Param 2([esp+8]:[esp+4])
//  Result is stored in edx:eax
//  O-flag set on exit   => result is invalid
//  O-flag clear on exit => result is valid
procedure __llmulo();
asm
  test    edx, edx           Param1-Hi = 0?
  jne     @@Large            No, More than one multiply may be needed
  cmp     edx, [esp+8]       Param2-Hi = 0?
  jne     @@Large            No, More than one multiply may be needed
  mul     dword ptr [esp+4]  Only one multiply needed, Set Result
  and     eax, eax           Clear Overflow Flag
  ret     8
@@Large:
  sub     esp, 28            allocate local storage
  mov     [esp], ebx         save used registers
  mov     [esp+4], esi
  mov     [esp+8], edi
  mov     [esp+12], ebp
  mov     ebx, [esp+32]      Param2-Lo
  mov     ecx, [esp+36]      Param2-Hi
  mov     esi, edx
  mov     edi, ecx
  sar     esi, 31
  sar     edi, 31
  xor     eax, esi
  xor     edx, esi
  sub     eax, esi
  sbb     edx, esi           edx:eax (a1:a0) = abs(Param1)
  xor     ebx, edi
  xor     ecx, edi
  sub     ebx, edi
  sbb     ecx, edi           ecx:ebx (b1:b0) = abs(Param2)
  xor     esi, edi           Sign Flag, 0 if Params have same sign else -1
  mov     [esp+16], eax      a0
  mov     [esp+20], edx      a1
  mov     [esp+24], ecx      b1
  mul     ebx                edx:eax (c1:c0) = a0*b0
  xchg    ebx, edx           ebx = c1, edx = b0
  mov     edi, eax           abs(Result-Lo) = c0
  xor     ecx, ecx           Upper 32 bits of 128 bit result
  xor     ebp, ebp           Second 32 bits of 128 bit result
  mov     eax, [esp+20]      a1
  mul     edx                edx:eax (d1:d0) = a1*b0
  add     ebx, eax           c1 + d0
  adc     ebp, edx           d1 + carry
  adc     ecx, 0             Possible carry into Upper 32 bits
  mov     eax, [esp+16]      a0
  mov     edx, [esp+24]      b1
  mul     edx                edx:eax (e1:e0) = a0*b1
  add     ebx, eax           abs(Result-Hi) = c1 + d0 + e0
  adc     ebp, edx           d1 + e1 + carry
  adc     ecx, 0             Possible carry into Upper 32 bits
  mov     eax, [esp+20]      a1
  mov     edx, [esp+24]      b1
  mul     edx                edx:eax (f1:f0) = a1*b1
  add     ebp, eax           d1 + e1 + f0 + carry
  adc     ecx, edx           f1 + carry
  or      ecx, ebp           Overflow if ecx <> 0 or ebp <> 0
  jnz     @@Overflow
  mov     edx, ebx           Set abs(Result-Hi)
  mov     eax, edi           Set abs(Result-Lo)
  cmp     edx, $80000000
  jae     @@CheckRange       Possible Overflow if edx>=$80000000
@@SetSign:
  xor     eax, esi           Correct Sign of Result
  xor     edx, esi
  sub     eax, esi
  sbb     edx, esi
  mov     ebx, [esp]         restore used registers
  mov     esi, [esp+4]
  mov     edi, [esp+8]
  mov     ebp, [esp+12]
  add     esp, 28            Clears Overflow flag
  ret     8
@@CheckRange:
  jne     @@Overflow         Overflow if edx>$80000000
  test    esi, esi           edx=$80000000, Is Sign Flag=0?
  jnz     @@SetSign          No, Result is Ok (-MaxInt64)
@@Overflow:
  mov     ebx, [esp]         restore used registers
  mov     esi, [esp+4]
  mov     edi, [esp+8]
  mov     ebp, [esp+12]
  add     esp, 28
  mov     ecx, $80000000
  dec     ecx                Set Overflow Flag
  ret     8
end;

【讨论】:

点评来源: 欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及它为什么存在,然后引用在目标页面不可用的情况下,您链接到的页面中最相关的部分。答案只不过是一个链接may be deleted。见:How do I write a good answer?

以上是关于Delphi - 检测 Int64 溢出错误的主要内容,如果未能解决你的问题,请参考以下文章

如何修复 OverflowError:int64 加法中的溢出

Delphi - URL 的文件大小 - 错误 12150

JNI 无法在 NetBeans 上检测到 __int64

如何在 GCC 中使用容器溢出错误检测?

RandomizedSearchCV 溢出错误:无法将“int”放入索引大小的整数中

运行时错误:有符号整数溢出:2147483647 + 1 不能以“int”类型表示