如何在 32 位机器上添加几个 32 位数字但不损失精度,即在 64 位“伪寄存器”eax:edx
中。使用 Intel 语法汇编器。
3 回答
假设您要添加的 32 位数字在 EAX 和 EBX 中,并且是无符号的:
xor edx,edx ;Set edx to zero
add eax,ebx
adc edx,0 ;edx:eax = eax + ebx
这与在加法之前将值零扩展为 64 位,然后进行 64 位加法基本相同。
对于有符号整数,这将不起作用(例如“0 + (-1) != 0x00000000 + 0xFFFFFFFF != 0x00000000FFFFFFFF”),因为您需要符号扩展而不是零扩展。要做到这一点:
cdq ;Set all bits in edx to the sign of eax
xchg ebx,eax ;eax = original ebx
mov ecx,edx ;ecx:ebx = original eax sign extended
cdq ;edx:eax = original ebx sign extended
add eax,ebx
adc edx,ecx ;edx:eax = eax + ebx
“可能更慢,具体取决于它是哪个 CPU”(见注)替代方法是通过向它们添加 0x80000000 来强制它们进入无符号整数的范围,然后通过从中减去 2*0x80000000(或 0x0000000100000000)来纠正结果。减 0x0000000100000000 和高 dword 减 1 一样,和高 dword 加 0xFFFFFFFF 一样,所以可以:
add eax,0x80000000
add ebx,0x80000000
xor edx,edx ;Set edx to zero
add eax,ebx
adc edx,0xFFFFFFFF ;edx:eax = eax + 0x80000000 + ebx + 0x80000000 + (-0x0000000100000000)
注意:如果您关心性能;这种替代方法使您有机会将 0x80000000 添加到早期代码中的值(无论值来自何处),并且通常可以更快地结束(特别是如果多次使用相同的 32 位值,和/或如果添加可以免费并入其他计算)。
对于“混合类型”,您只需要将有符号值提升为 64 位。例如,如果 EAX 已签名而 EBX 未签名:
cdq ;Set all bits in edx to the sign of eax
add eax,ebx
adc edx,0 ;edx:eax = eax + ebx
当然,对于较新的 CPU,您将使用 64 位代码。对于无符号 32 位值,默认情况下它们已经是零扩展的,您只需要一条add rax,rbx
指令。对于已签名的号码,您可能需要签署扩展(如果您不能/没有事先签署扩展它们),例如:
movsx rax,eax
movsx rbx,ebx
add rax,rbx
如果我正确理解了这个问题,您有两个 32 位整数相加,可能会给出一个 64 位整数。您想在没有 32 位溢出的情况下执行此操作。
看看编译器做了什么:
$ cat add64.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main (int argc, char **argv) {
uint32_t a, b;
a = strtoll(argv[1], NULL, 10);
b = strtoll(argv[2], NULL, 10);
printf("%lu + %lu = %llu\n", a, b, (uint64_t) a + b);
return 0;
}
$ gcc -m32 -g -o add64 add64.c
$ ./add64 3000000000 4000000000
3000000000 + 4000000000 = 7000000000
$ gcc -m32 -g -fverbose-asm -masm=intel -S add64.c
$ $EDITOR add64.s &
[5] 340
$
相关生成的程序集是:
mov %ecx, DWORD PTR [%ebp-16] # D.2300, a
mov %ebx, 0 # D.2300,
mov %eax, DWORD PTR [%ebp-12] # D.2301, b
mov %edx, 0 # D.2301,
add %eax, %ecx # D.2302, D.2300
adc %edx, %ebx # D.2302, D.2300
为了在 32 位机器上添加 64 位数字,您必须首先将 64 位数字的上半部分移动到寄存器 eax,然后将后半部分移动到 edx。操作此数字时,您必须跟踪该数字在 eax/edx 中的放置方式。