我对x86 Intel大会还很陌生。目前,我正在尝试获取用户传递的数字的绝对值。目前,该代码只是将传回的数字吐出。如果它是-5,那么程序会指出它的绝对值仍然是-5。如果它是5,则程序声明其绝对值为5。我试图使用条件语句,并且仅跳转到一段代码,当我们的数字小于零时,该代码段将翻转我们的符号位。我不认为我正确地跳到了这段代码,不知道为什么。到目前为止,我的代码如下:
#compare with 0 and if less then flip sign
#Syntax: int abs_val(int num)
#Returns the absolute value of num
abs_val:
#num will be stored in %rdi
push %rbp
mov %rsp, %rbp
cmpq $0, %rdi
jl signFlip
leave
ret
signFlip:
neg %rdi
mov %rdi, %rax
leave
ret
请注意:我不是在找别人为我输入完整的解决方案。我希望能朝着正确的方向前进,例如:“您需要使用xyz函数调用而不是neg”或“您没有正确存储值,请尝试将其移入该变量中”
提前谢谢!
答案 0 :(得分:2)
您也用C声明了这个int
吗? int
是您正在使用的x86-64 System V ABI中的32位类型。
但是在告诉编译器arg的高32位无关紧要之后,您正在对qword long
操作数进行操作。因此,当您将-5
作为int
arg传递时,您会在RDI中得到0x00000000fffffffb
,它是64位2的补码正整数4294967291。(这是假定高位是零,例如,如果调用方像C编译器那样使用mov $-5, %edi
并使用一个常量arg,但是如果将long
强制转换为int
,则可能会得到任意垃圾:调用方将假定函数会忽略广告中的高位。)
如果arg为正,则您忘记了mov %rdi, %rax
。 (然后执行条件neg %rax
)或实际上是mov %edi, %eax
/ neg %eax
。
因此,当RDI为正时,您的函数将RAX保持不变。 (但是由于调用者可能是gcc -O0
中未优化的C语言,因此它恰好还保存了arg的副本。这解释了abs(-5)
=> -5
的行为,而不是半-您希望从此类错误中得到的随机垃圾)。
x86-64 System V调用约定不是要求调用者对扩展的args进行符号扩展或将值返回为完整的寄存器宽度,因此您的cmp
/ jl
取决于调用方留在RDI上半部分的任何垃圾。
(Is a sign or zero extension required when adding a 32bit offset to a pointer for the x86-64 ABI?)。没有迹象表明符号或零的行为会将窄args扩展到32位,而不是64位。
大多数操作的自然/默认大小为32位操作数大小,64位地址大小,并且在写入32位寄存器时具有隐式零扩展为64位。除非您有使用64位的特定原因(例如,对于指针),否则请使用32位操作数大小。
看看编译器通常会做什么;他们通常将cmov
用于无分支的abs()
。请参见启用了优化功能的C ++编译器的long foo(long x) { return std::abs(x); }
的编译器输出,例如https://godbolt.org/z/I3NSIZ适用于long
和int
版本。
# clang7.0 -O3
foo(int): # @foo(int)
movl %edi, %eax
negl %eax # neg sets flags according to eax = 0-eax
cmovll %edi, %eax # copy the original if that made it negative
retq
# gcc7.3 -O3
foo(int):
movl %edi, %edx
movl %edi, %eax
sarl $31, %edx
xorl %edx, %eax # 2's complement identity hack. Maybe nice with slow cmov
subl %edx, %eax
ret
但是不幸的是,即使-march=skylake
是1 uop,gcc也不会切换到cmov。