使用 Assembly 实现纠正溢出情况

Correcting overflow situations with Assembly implementation

上下文:

我目前正在学习使用 Intel IA32 x86 处理器的 Linux 发行版的汇编,我一直在努力了解如何处理溢出问题。

我相信我理解它的理论,但我还没有设法为溢出情况实施解决方案。

我在网上搜索过,我找到的页面解释了标志系统并且 how/when 它们激活了。那不是我感兴趣的。 我想知道如何 "solve" 它。

实际上,当我写这篇文章的时候,我仔细地、慢慢地思考,我相信我理解了解决方案。所以现在我实际上只想知道我是在直想还是有更好的方法。

问题:

在以下情况下可能会发生溢出:

为简单起见,让我们考虑两个带符号的字符(1 字节)。我知道结果将需要更多位,因此让我们将结果视为 short int(2 字节).

数据:

op1 = 0x7F      # Bit representation: 0111 1111     Decimal representation: +127
op2 = 0x01      # Bit representation: 0000 0001     Decimal representation: +  1
op3 = 0x80      # Bit representation: 1000 0000     Decimal representation: -128
op4 = 0xff      # Bit representation: 1111 1111     Decimal representation: -  1

示例 A:

movl [=11=], %eax    # Set all bits to 0
movb op1, %al   # Move op1 to the al register
addb op2, %al   # Adding two positives that activates the Overflow flag
                # Deactivates the Carry Flag
                # Current bit representation in ax:  0000 0000 1000 0000
                # Expected bit representation in ax: 0000 0000 1000 0000
                # The simple fact of consider the short int (which contains 0s) fixes the issue. || (+127) + (+1) = (+128)
ret             # Returns ax

示例 B:

movl [=12=], %eax    # Set all bits to 0
movb op3, %al   # Move op3 to the al register
addb op4, %al   # Adding two negative numbers that activates the Overflow flag
                # Activates Carry Flag
                # Current bit representation in ax:  0000 0000 0111 1111
                # Expected bit representation in ax: 1111 1111 0111 1111
notb %ah        # The "not" instruction on the most significative byte resolves the issue since it inverts all bits. || (-128) + (-1) = (-129)
ret             # Returns ax

示例 C:

movl [=13=], %eax    # Set all bits to 0
movb op1, %al   # Move op1 to the al register
subb op4, %al   # Subtracting a negative number to a positive number that activates the Overflow flag
                # Activates the Carry Flag
                # Current bit representation in ax:  0000 0000 1000 0000
                # Expected bit representation in ax: 0000 0000 1000 0000
                # The simple fact of consider the short int (which contains 0s) fixes the issue. || (+127) - (-1) = (+128)
ret             # Returns ax

示例 D:

movl [=14=], %eax    # Set all bits to 0
movb op3, %al   # Move op3 to the al register
subb op2, %al   # Subtracting a positive number to a negative number that activates the Overflow flag
                # Deactivates Carry Flag
                # Current bit representation in ax:  0000 0000 0111 1111
                # Expected bit representation in ax: 1111 1111 0111 1111
notb %ah        # The "not" instruction on the most significative byte resolves the issue since it inverts all bits. || (-128) - (+1) = (-129)
ret             # Returns ax

对于我上面所说的一切,我总结出以下两个功能:

添加:

addition:
    movl [=15=], %eax    # Set all bits to 0
    movb opX, %al   # Move opX to the al register
    addb opY, %al   # Adding two numbers
    jc overflow     # Jumps if Carry Flag is on
end:
    ret             # Returns ax
overflow:
    notb %ah        # Inverts most significative bits
    jmp end         # Jumps back to the end of the function

对于减法:

subtraction:
    movl [=16=], %eax    # Set all bits to 0
    movb opX, %al   # Move opX to the al register
    subb opY, %al   # Subtracting two numbers
    jc end          # Jumps if Carry Flag is on
    notb %ah        # Inverts most significative bits
end:
    ret             # Returns ax

我相信即使使用不会导致溢出的数字,这些函数也能正常工作。

问题:

1st:我上面说的有没有错?

2nd: 如果我遇到了,在哪里以及如何解决?如果我没有,有没有更好的方法来解决这个问题?

3rd:这个会不会干扰其他不会导致溢出的加减法?

4th:为什么我没有在任何地方使用溢出标志?

您可能有点忽略了溢出通常是想要的行为这一点(由于简单+廉价的硬件实现加上高性能)。

即。如果程序员希望得到像 150 + 150 这样的数字,则使用 16 位算术取决于程序员,并且只有当他们知道结果将在 0..255 或 -128..+127 内,或者如果他们不知道时,才继续使用 8 位算术不要介意溢出时截断的结果(在某些情况下是有效的使用模式)。

你的 4->8 扩展没有多大意义,通常有 32b 个整数就足够了,根本不会溢出。当出现 2+GiB RAM 和文件时,该假设确实有害,使一些安装程序报告可用 RAM 为 -2GB 并要求更多 :),或仅复制 3GiB 文件的一部分。

所以第二个例子应该改为(我的 AT&T 语法不是很好,对于可能的语法错误很抱歉),指望你的数据不会随着有效输入数据溢出,作为有效数据适合 16b 结果:

movsbw op3, %ax    # Move op3 to the ax register, sign extend it from 8b to 16b
movsbw op4, %bx    # sign-extend op4 to 16b as well
addw %bx, %ax      # Adding two negative numbers, not caring about OF/CF, expecting valid result
                # Current bit representation in ax:  1111 1111 0111 1111
  # note the eax is: ???? ???? ???? ???? 1111 1111 0111 1111
  # high 16b are undefined, and if you would clear eax, they would be 0 anyway
ret

照顾CF/OF会大大降低性能,并且仅在您确实必须验证计算的特殊情况下使用。大多数 SW 愉快地生活在 "garbage in, garbage out" 前提下(通常效果很好),并且只为 valid/expected 输入产生 valid/expected 输出。

进行所有计算 "safe/correct" 有点毫无意义(或者更确切地说 "expensive"),因为计算机的资源非常有限,而且无论如何您都无法使用计算机处理大数。本机 64b CPU 可以使用 64 或 128b 整数进行算术运算,除此之外,您可以使用可用内存大小的整数实现非本机操作,但迟早(可能非常晚,考虑到 32GiB 的操作内存)你会遇到点,在那里你会溢出足够大的整数(本例中为 28e32G)。自然地,你已经完成了 264/128/256+ 值甚至更快,这对于整数来说甚至不是那么多,尽管对于普通人来说甚至只是 232通常就够了。