在机器代码中区分有符号和无符号

amj*_*jad 1 c assembly unsigned

我在看一本教科书,上面写着:

重要的是要注意机器代码如何区分有符号和无符号值。与 C 不同,它不将数据类型与每个程序值相关联。相反,它主要对这两种情况使用相同的(汇编)指令,因为许多算术运算对于无符号和补码算术具有相同的位级行为。

我不明白这是什么意思,谁能给我举个例子?

use*_*733 5

例如,这段代码:

int main() {
    int i = -1;
    if(i < 9)
        i++;
    unsigned u = -1;  // Wraps around to UINT_MAX value
    if(u < 9)
        u++;
}
Run Code Online (Sandbox Code Playgroud)

在 x86 GCC 上给出以下输出:

main:
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-4], -1 ; i = -1
    cmp     DWORD PTR [rbp-4], 8  ; i comparison
    jg      .L2                   ; i comparison
    add     DWORD PTR [rbp-4], 1  ; i addition
.L2:
    mov     DWORD PTR [rbp-8], -1 ; u = -1
    cmp     DWORD PTR [rbp-8], 8  ; u comparison
    ja      .L3                   ; u comparison
    add     DWORD PTR [rbp-8], 1  ; u addition
.L3:
    mov     eax, 0
    pop     rbp
    ret
Run Code Online (Sandbox Code Playgroud)

请注意它如何对变量和使用相同的初始化 ( mov) 和增量 ( add)指令。这是因为无符号和 2 的补码的位模式变化相同。iu

比较也使用相同的指令cmp,但跳转决定必须不同,因为设置最高位的值在类型上是不同的:(jg如果更大则跳转)在有符号上,ja(如果大于则跳转)在无符号上。

选择什么指令取决于体系结构和编译器。

  • 如果你使用 `i` 和 `u` 函数参数,你可以避免所有未优化代码的噪音,所以你可以在启用优化的情况下编译,而不是让它们优化掉。(如果您返回或存储结果)。请参阅 https://godbolt.org/z/muqTFb。(我使用 C++ `int &amp;i` 通过引用传递而不将所有内容更改为 `*i`)。 (2认同)

Ale*_*lke 5

在 Intel 处理器(x86 系列)和其他具有 的处理器上FLAGS,您可以从那些FLAGS告诉您最后一个操作如何工作的位中获得信息。处理器之间的名称FLAGS略有不同,但一般来说,在算术方面有两个重要的名称:CFOF

CF是进位位(通常C在其他处理器上称为)。

OF是溢出位(通常V在其他处理器上称为)。

或多或少,CF代表无符号溢出,OF代表有符号溢出。当处理器执行ADD操作时,它有一位额外的位,即CF。因此,如果将两个 64 位数字相加,则未换行的结果可能需要 65 位。这就是进位。该OF标志被设置为最高位(即 64 位数字中的位 63),对两个源和目标中的该位使用 3 个逻辑运算。

有一个关于如何CF使用 4 位寄存器的示例:

R1 = 1010
R2 = 1101

R3 = R1 + R2 = 1 0111
               ^
               +---- carry (CF)
Run Code Online (Sandbox Code Playgroud)

额外的部分1不适合 R3,因此它被放入位中CF。附带说明一下,MIPS 处理器没有任何FLAGS. 由您决定是否生成进位(您可以在两个源和目标上使用 XOR 等来执行此操作)。

但是,在 C(和 C++)中,没有验证整数类型的溢出(至少默认情况下没有)。因此,换句话说,除了CF四个OF比较运算符(<<=>>=)。

如@user694733 提供的示例所示,区别在于是否使用ajg或。ja16条跳转指令中的每一条都会测试各种标志来判断是否跳转。这种组合确实是与众不同的。

ADC另一个有趣的方面是和之间的区别ADD。在一种情况下,您需要添加进位,而另一种情况则不添加。现在我们有 64 位计算机,它可能不会被广泛使用,但是要使用 32 位处理器添加两个 64 位数字,它将把低 32 位添加为无符号 32 位数字,然后添加高 32 位数字(有符号或无符号(视情况而定)加上第一次操作的进位。

假设 32 位寄存器中有两个 64 位数字 (ECX:EAXEDX:EBX),您可以像这样添加它们:

ADD EAX, EBX
ADC ECX, EDX
Run Code Online (Sandbox Code Playgroud)

这里,如果有无符号溢出EDX,则将 和 进位相加(进位——意味着现在应该用33位来表示 和 ,因为结果不适合 32 位,标志是第 33 位)。ECXEAX + EBXEAXEBX CF

需要注意的是,英特尔处理器具有:

  • 零位:(ZF无论结果是否为零,)
  • CFSBC减去 (for , SBB,)时称为“借位”并且
  • 还有AF用于“十进制数运算”的位(头脑清醒的人不会使用它。)该AF位告诉您十进制运算中存在溢出。类似的事情。我从来没有用过那个。我发现它们的使用太复杂/麻烦。此外,该位在 amd64 中仍然存在,但设置它的指令已被删除(参见DAA示例)。