可变参数在 x86-64 上的工作方式实际上相当复杂。
如果我们以此为例:
#include <stdio.h>
int main()
{
double f=0.7;
printf("%d %f %p %d %f", 17, f, "hello", 42, 0.8);
return 0;
}
它生成的代码是:
.file "printf.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "hello"
.LC3:
.string "%d %f %p %d %f"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $42, %ecx
movl $.LC1, %edx
movsd .LC0(%rip), %xmm1
movl $17, %esi
movsd .LC2(%rip), %xmm0
movl $.LC3, %edi
movl $2, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC0:
.long 2576980378
.long 1072273817
.align 8
.LC2:
.long 1717986918
.long 1072064102
.ident "GCC: (GNU) 4.6.3 20120306 (Red Hat 4.6.3-2)"
.section .note.GNU-stack,"",@progbits
如您所见,浮点值保存在%xmm0
and中%xmm1
,并且该函数(与任何其他 varargs 函数一样)通过(在本例printf
中为 2)中的值“告诉”在 SSE 寄存器中传递了多少参数。%eax
常规参数在寄存器中传递,因此%edi
, %esi
, %edx
,%ecx
包含格式字符串、第一个整数参数、地址"hello"
和第二个整数参数。这遵循 x86_64 的标准参数顺序。
编译器通常会生成代码,然后将所有参数寄存器压入堆栈,并“捞出”va*
函数中的寄存器。
因此,如果我们将上面的源代码替换为printf
a myprintf
,如下所示:
void myprintf(const char *fmt, ...)
{
va_list va;
int i;
va_start(va, fmt);
for(i = 0; i < 5; i++)
{
switch(i)
{
case 1:
case 4:
{
double d = va_arg(va, double);
printf("double %f:", d);
}
break;
default:
{
long l = va_arg(va, long);
printf("long %ld:", l);
}
}
}
printf("\n");
}
在myprintf
它的开头:
...
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
je .L2
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.L2:
...
然后从堆栈中取出东西的代码非常复杂。这是浮点方面:
.L4:
.cfi_restore_state
movl 12(%rsp), %edx
cmpl $176, %edx
jae .L5
movl %edx, %eax
addq 24(%rsp), %rax
addl $16, %edx
movl %edx, 12(%rsp)
.L6:
movsd (%rax), %xmm0
movl $.LC0, %edi
movl $1, %eax
call printf
jmp .L7
.p2align 4,,10
.p2align 3
.L8:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L9
.p2align 4,,10
.p2align 3
.L5:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L6
现在,我不知道您使用的是什么编译器标志,因为我的编译器可以gcc -O2 -nostdlib -fno-builtin -ffreestanding
毫无问题地生成此代码。