计算负数时 VB6 中的奇怪行为 Mod 一个 2 的幂数

Strange behavior in VB6 when calculating a negative number Mod a number which is a power of 2

我正在调试一段用 VB6 开发的旧代码,发现了一些非常古怪的东西运行ge。可以用下面的简单代码来演示:

Private Sub Command1_Click()
    Dim a As Integer
    Dim b As Integer
    Dim c As Integer

    a = 0
    Text1.Text = a - 1
    Text2.Text = CStr((a - 1) Mod 4)

    b = 0
    b = b - 1
    Text3.Text = b
    Text4.Text = CStr(b Mod 4)

    c = 0 - 1
    Text5.Text = c
    Text6.Text = CStr(c Mod 4)

End Sub

表单如下所示:

你会认为点击按钮后Text2、Text4和Text6应该显示相同的内容,即-1。当我在 IDE.

中按 F5 到 运行 时就是这种情况

这是我在 IDE 中 运行 时的样子:

当我从 IDE 和 运行 exe 本身制作一个 exe 时,发生了 st运行ge 事情。 Text1 和 Text6 显示 -1 但 Text4 显示 3.

这是我 运行 IDE 之外的 exe 时的样子:

只有当第二个 ope运行d 是 2 的幂时才会发生这种情况。当我将 4 更改为 5 时,所有文本框都显示 -1。

我在 2 Windows 10 台机器上测试了这个并得到了相同的结果。

我知道 VB6 很旧,现在没有多少人仍然可以使用它进行测试。如果有人能帮助我理解这一点,我将不胜感激。

谢谢。

这似乎是一个编译器错误。编译器无法识别在 Integer(16 位值)的情况下该值是有符号的,但是 Long.

的情况下尊重它

执行模 4 的代码在每种情况下几乎相同,并且遵循 optimized pattern for modulo powers of 2:

长(b Mod 4&):

or   eax, 0xFFFFFFFF  # eax = 0xFFFFFFFF (which is -1)
and  eax, 0x80000003  # eax = 0x80000003 The modulo op, note it's signed because of the 8

jns  other_code       # Skip the next three lines if the result is non-negative (it isn't here)

dec  eax              # eax = 0x80000002
or   eax, 0xFFFFFFFC  # eax = 0xFFFFFFFE
inc  eax              # eax = 0xFFFFFFFF (which is -1)

other_code:

最后eax包含0xFFFFFFFF,也就是-1,传递给显示。


整数(b Mod 4):

or   eax, 0xFFFFFFFF  # eax = 0xFFFFFFFF, ax = 0xFFFF (which is -1 in both cases)
and  ax,  0x3         # eax = 0xFFFF0003, ax = 0x0003. Should have been "and ax, 0x8003!"

jns  other_code       # Skip the next three lines if the result is non-negative (it incorrectly is)

dec  ax               # Skipped
or   ax,  0xFFFC      # Skipped
inc  ax               # Skipped

other_code:

最后 eax 包含 0xFFFF0003 然后传递给 __vbaStrI2 函数显然会忽略两个高字节并且只使用 0003.

如果使用 and ax, 0x8003 而不是 and ax, 0x3,则跳过的行将触发并将 0xFFFF0003 转换为 0xFFFFFFFF,即 -1。


在禁用优化的情况下,按位取模数学被简单的除法代替:

sub   ax, 0x1          # b = b - 1
mov   cx, 0x4          # Prepare division by 4
idiv  cx               # Integer division

至于为什么 ac 的情况按预期工作,这是因为编译器在编译阶段计算 -1 Mod 4 并将结果硬编码到可执行文件中:

push  0xFFFFFFFF       # Pass hardcoded -1 for display

other_code:

从技术上讲,没有什么可以阻止它在 b 的情况下做同样的事情,因为它也可以证明被划分的值是 -1。我无法确定它为什么不这样做——也许它过早停止了静态分析一步,或者可能将 -1 写回 b 的内存地址的结果代码是它认为效率较低。