计算负数时 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
至于为什么 a
和 c
的情况按预期工作,这是因为编译器在编译阶段计算 -1 Mod 4
并将结果硬编码到可执行文件中:
push 0xFFFFFFFF # Pass hardcoded -1 for display
other_code:
从技术上讲,没有什么可以阻止它在 b
的情况下做同样的事情,因为它也可以证明被划分的值是 -1
。我无法确定它为什么不这样做——也许它过早停止了静态分析一步,或者可能将 -1
写回 b
的内存地址的结果代码是它认为效率较低。
我正在调试一段用 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
至于为什么 a
和 c
的情况按预期工作,这是因为编译器在编译阶段计算 -1 Mod 4
并将结果硬编码到可执行文件中:
push 0xFFFFFFFF # Pass hardcoded -1 for display
other_code:
从技术上讲,没有什么可以阻止它在 b
的情况下做同样的事情,因为它也可以证明被划分的值是 -1
。我无法确定它为什么不这样做——也许它过早停止了静态分析一步,或者可能将 -1
写回 b
的内存地址的结果代码是它认为效率较低。