在x86_64 ABI中,如果一个函数有可变参数,则期望
AL
(它是
EAX
的一部分)保存用于保存该函数参数的向量寄存器数量。
在您的示例中:
printf("%d", 1);
这个函数有一个整数参数,所以不需要使用向量寄存器,因此将 AL
设置为0。
另一方面,如果您将示例更改为:
printf("%f", 1.0f);
然后浮点字面量将被存储在向量寄存器中,相应地,AL
被设置为 1
:
movsd LC1(%rip), %xmm0
leaq LC0(%rip), %rdi
movl $1, %eax
call _printf
正如预期:
printf("%f %f", 1.0f, 2.0f);
由于有两个浮点参数,这将导致编译器将 AL
设置为 2
:
movsd LC0(%rip), %xmm0
movapd %xmm0, %xmm1
movsd LC2(%rip), %xmm0
leaq LC1(%rip), %rdi
movl $2, %eax
call _printf
关于您的其他问题:
puts在调用之前也将%eax清零,尽管它只需要一个指针。为什么会这样?
不应该这样。例如:
#include <stdio.h>
void test(void) {
puts("foo");
}
当使用gcc -c -O0 -S
编译时,输出:
pushq %rbp
movq %rsp, %rbp
leaq LC0(%rip), %rdi
call _puts
leave
ret
而且%eax
没有被清零。但是,如果您删除#include <stdio.h>
,则生成的汇编代码会在调用puts()
之前将%eax
清零:
pushq %rbp
movq %rsp, %rbp
leaq LC0(%rip), %rdi
movl $0, %eax
call _puts
leave
ret
原因与您的第二个问题有关:
这也发生在调用我的void proc()函数之前(即使设置了-O2),但在调用void proc2(int param)函数时不会清零。
如果编译器没有看到函数的声明,则不会对其参数做出任何假设,该函数可能接受可变参数。如果指定空参数列表(不应该这样做,并且被ISO/IEC标记为过时的C特性),同样适用。由于编译器没有足够的有关函数参数的信息,因此在调用函数之前将%eax清零,因为函数可能被定义为具有可变参数。
例如:
#include <stdio.h>
void function() {
puts("foo");
}
void test(void) {
function();
}
当 function()
拥有一个空参数列表时,会产生以下结果:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
call _function
leave
ret
然而,如果您遵循推荐的做法,在函数不接受任何参数时指定void
,例如:
#include <stdio.h>
void function(void) {
puts("foo");
}
void test(void) {
function();
}
那么编译器就知道function()
不接受参数,特别是它不接受可变参数,因此在调用该函数之前不会清除%eax
:
pushq %rbp
movq %rsp, %rbp
call _function
leave
ret
%rax
寄存器中的优点是什么?它仅仅是为了提高性能,避免在“寄存器保存区域”上保存无用的寄存器吗? - Ciro Santilli OurBigBook.com