我在EDX:EAX
寄存器对中存储了一个 64位整数。
我怎样才能正确 否定数字?
例如:123456789123
→-123456789123
。
答案 0 :(得分:10)
询问编译器的想法:在32位模式下编译int64_t neg(int64_t a) { return -a; }
。当然,询问编译器的不同方式将在内存中,编译器的寄存器选择中,或者已经在EDX:EAX中具有起始值。查看所有三种方式on the Godbolt compiler explorer,使用gcc,clang和MSVC(又名CL)的asm输出。
当然有很多方法可以实现这一点,但任何可能的顺序都需要某种程度的从低到高的进位,因此没有有效的方法来避免SBB或ADC。
如果值在内存中开始,或者您想将原始值保留在寄存器中,请将目标xor-zero并使用SUB / SBB。 SysV x86-32 ABI在堆栈上传递args并在EDX:EAX中返回64位整数。这是neg_value_from_mem
的{{1}}:
; optimal for data coming from memory: just subtract from zero
xor eax, eax
xor edx, edx
sub eax, dword ptr [esp + 4]
sbb edx, dword ptr [esp + 8]
如果您在寄存器中有值并且不需要就地结果,则可以使用clang3.9.1 -m32 -O3
does将寄存器设置为0 - 本身,设置CF iff输入非零。即与SUB相同的方式。请注意NEG,而不是延迟关键路径的一部分,所以这肯定比gcc的3指令序列(下面)好。
;; partially in-place: input in ecx:eax
xor edx, edx
neg eax ; eax = 0-eax, setting flags appropriately
sbb edx, ecx ;; result in edx:eax
Clang甚至为就地案件做到这一点,即使这需要额外mov ecx,edx
。这对于具有零延迟mov reg,reg(Intel IvB +和AMD Zen)的现代CPU的延迟是最佳的,但不适用于融合域uops(前端吞吐量)或代码大小的数量。
gcc的序列很有趣,并不完全明显。它为就地案件节省了指令与铿锵声,但情况更糟。
; gcc's in-place sequence, only good for in-place use
neg eax
adc edx, 0
neg edx
; disadvantage: higher latency for the upper half than subtract-from-zero
; advantage: result in edx:eax with no extra registers used
不幸的是,gcc和MSVC都使用它,即使xor-zero + sub / sbb会更好。
要更全面地了解编译器的作用,请查看这些函数的输出(xor-zeroing is cheap)
#include <stdint.h>
int64_t neg_value_from_mem(int64_t a) {
return -a;
}
int64_t neg_value_in_regs(int64_t a) {
// The OR makes the compiler load+OR first
// but it can choose regs to set up for the negate
int64_t reg = a | 0x1111111111LL;
// clang chooses mov reg,mem / or reg,imm8 when possible,
// otherwise mov reg,imm32 / or reg,mem. Nice :)
return -reg;
}
int64_t foo();
int64_t neg_value_in_place(int64_t a) {
// foo's return value will be in edx:eax
return -foo();
}