2 的补码只是十进制数和二进制数之间的映射。
编译器通过将文字数字转换为相应的二进制文件来实现此映射,例如 -3 到 0xFFFFFFFD(可以在反汇编中看到),并生成与 2 的补码表示一致的机器代码。例如,当它试图执行 0-3 时,它应该选择一个指令,该指令应该通过将 0x00000000 和 0x000000003 作为参数来产生 0xFFFFFFFD。
它选择与无符号减法相同的 SUB 的原因是它只是按预期产生 0xFFFFFFFD。无需要求 CPU 为有符号减法提供特殊的 SUB。说第二个操作数被 2 的补码取反,由此推断出 CPU 在 SUB 中实现 2 的补码的结论是不公平的。因为在减法中从高位借位恰好与 2 的补码反转相同,并且 SUB 也用于无符号减法,所以根本不需要在 SUB 中涉及 2 的补码的概念。
下面的反汇编说明了有符号减法使用与无符号相同的 SUB 的事实。
//int32_3 = -3;
010B2365 mov dword ptr [int32_3],0FFFFFFFDh
//int32_1 = 0, int32_2 = 3;
010B236C mov dword ptr [int32_1],0
010B2373 mov dword ptr [int32_2],3
//uint32_1 = 0, uint32_2 = 3;
010B237A mov dword ptr [uint32_1],0
010B2384 mov dword ptr [uint32_2],3
//int32_3 = int32_1 - int32_2;
010B238E mov eax,dword ptr [int32_1]
010B2391 sub eax,dword ptr [int32_2]
010B2394 mov dword ptr [int32_3],eax
//uint32_3 = uint32_1 - uint32_2;
010B2397 mov eax,dword ptr [uint32_1]
010B239D sub eax,dword ptr [uint32_2]
010B23A3 mov dword ptr [uint32_3],eax
CPU 会在 CF 和 OF 标志中保留额外的信息,以便进一步的指令根据分配给结果的变量类型以不同的方式使用 SUB 的结果。
以下反汇编说明了编译器如何为有符号比较和无符号比较生成不同的指令。Noticecmp
包括一个 internal sub
,并且jle
基于 OF 标志和jbe
基于 CF 标志。
//if (int32_3 > 1) int32_3 = 0;
010B23A9 cmp dword ptr [int32_3],1
010B23AD jle main+76h (010B23B6h)
010B23AF mov dword ptr [int32_3],0
//if (uint32_3 > 1) uint32_3 = 0;
010B23B6 cmp dword ptr [uint32_3],1
010B23BD jbe main+89h (010B23C9h)
010B23BF mov dword ptr [uint32_3],0
正是 OF 放弃了 CPU 实现 2 的补码的事实,因为设置 OF 的方式是在超过中间二进制数 0x10000000 或 0x0FFFFFFF 时。而 2 的补码表示将 0x10000000 映射到 -268435456 和 0x0FFFFFFF 到 268435455,它们是 32 位整数的上限和下限。所以这个OF标志是专门为2的补码设计的,因为其他表示可能会选择将其他二进制数映射到上限和下限。
总结: 1. 编译器通过实现相应的表示(映射)和生成指令来区分有符号和无符号算术,其结果符合编译器对有符号和无符号整数的表示。2、编译器实现了2的补码表示,CPU也实现了它以支持编译器生成算术指令,其结果符合2的补码表示。