2011-06-02 153 views
21

我想拿起一个小x86。我正在用gcc -S -O0编译64位mac。为什么%eax在调用printf之前清零?

代码在C:

printf("%d", 1); 

输出:

movl $1, %esi 
leaq LC0(%rip), %rdi 
movl $0, %eax  ; WHY? 
call _printf 

我不明白为什么 '的printf' 调用之前%EAX清零。由于printf返回打印到%eax的字符数量,我最好猜测它已被清零,为printf做好准备,但我会认为printf必须负责准备。另外,相比之下,如果我打电话给我自己的功能int testproc(int p1),gcc看到没有必要准备%eax。所以我想知道为什么gcc以不同的方式处理printftestproc

回答

21

x86_64 System V ABI

Register Usage 
%rax  temporary register; with variable arguments 
      passes information about the number of vector 
      registers used; 1st return register 
... 

printf是具有可变参数的函数,以及用于矢量寄存器的数目是零。

请注意,printf必须只检查%al,因为调用者被允许在%rax的高字节中留下垃圾。 (不过,xor %eax,%eax是最有效的方式,零%al

this Q&A标签维基了解更多详情,或长达最新ABI链接,如果上面的链接已过时。

+0

'puts'也归零'% eax'就在通话之前,尽管它只需要一个指针。为什么是这样? – sh54 2011-06-02 20:00:25

+0

这也发生在任何调用我自己的'void proc()'函数(即使使用-O2集合)之前,但是在调用'void proc2(int param)'函数时它不会被调零。 – sh54 2011-06-02 20:07:06

+2

为了记录,它发生在调用'void proc()'之前,因为C中的签名实际上没有提及proc的arity,它也可能是一个可变参数函数,所以调零rax是必要的。 'void proc()'与'void proc(void)'不同。请参阅http://stackoverflow.com/questions/693788/-void-arguments – frangio 2015-04-21 13:27:45

32

在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); 

将导致编译器设置AL2因为有两个浮点参数:

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>然后将所得的组件不零出%eax正确调用puts()前:

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(); 
} 

the n中的编译器知道function()不接受参数 - 特别是,它不接受变量参数 - 因此并不清楚%eax调用该函数之前:

pushq %rbp 
movq %rsp, %rbp 
call _function 
leave 
ret 
+1

来自ABI的注意事项:“我们使用向量寄存器来指代SSE或AVX寄存器。” – 2015-07-20 15:06:21

+1

在'%rax'中传递向量数的好处是什么?是否仅限于性能,避免在“注册保存区域”上保存无用的寄存器? – 2015-07-20 15:42:00

相关问题