cdecl(代表C声明)是一种调用约定,它起源于C编程语言,许多C编译器都将它用于x86体系结构。在cdecl中,子例程参数在堆栈中传递。在EAX寄存器中返回整数值和内存地址,在ST0 x87寄存器中返回浮点值。寄存器EAX、ECX和EDX被调用保存,其余的被调用保存。当调用新函数时,x87浮点寄存器ST0到ST7必须为空(弹出或释放),而ST1到ST7在退出函数时必须为空。ST0在不用于返回值时也必须为空。
在C编程语言的上下文中,函数参数按从右到左的顺序被推送到堆栈上,也就是说,最后一个参数首先被推送。在Linux中,GCC为调用约定设置了事实上的标准。由于GCC版本4.5,调用函数时堆栈必须对齐到16字节的边界(以前的版本只需要4字节的对齐)。
考虑以下源代码:
int callee(int, int, int);
int caller(void){
return callee(1, 2, 3) + 5;
}
在X86架构编译器上,他可能会产生以下的汇编代码:
caller:
push ebp ;
mov ebp, esp ;
push 3
push 2
push 1
call callee
add eax, 5
mov esp, ebp
pop ebp
ret
函数调用返回后,调用方清理堆栈。
cdecl的解释有一些变化,特别是在如何返回值方面。因此,为不同的操作系统平台和/或由不同的编译器编译的x86程序可能不兼容,即使它们都使用“cdecl”约定且不调用底层环境。在寄存器对EAX:EDX中,一些编译器返回长度为2个寄存器或更短的简单数据结构,并且在内存中返回需要异常处理程序(例如,定义的构造函数、析构函数或赋值)特殊处理的更大的结构和类对象。要传递“in memory”,调用者分配内存,并将指针作为隐藏的第一个参数传递给内存;被调用方填充内存并返回指针,在返回时弹出隐藏指针。
在Linux/GCC中,双/浮点值应该通过x87伪栈推送到堆栈上。像这样:
sub esp, 8
fld [ebp + x]
fstp [esp]
call funct
add esp, 8
使用此方法可以确保以正确的格式将其推送到堆栈上。
cdecl调用约定通常是x86 C编译器的默认调用约定,尽管许多编译器提供选项来自动更改所使用的调用约定。要手动定义cdecl函数,一些函数支持以下语法:
return_type _cdecl func_name();