__cdecl调用
在C语言中,函数调用支持不定参数,例如printf函数,可以不知道参数的个数,eg:
void err_info(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
这是如何实现的呢?如何支持变参的呢?
下面简要介绍下__cdecl调用方式,当然还有很多其他的调用方式,不过目前用不到
函数调用需要关注的几个方面:
1、函数调用时参数是如何保存的?
2、是调用者来清理函数调用还是被调用者来清理函数调用?
3、函数调用时参数的顺序是怎样的?
4、可能会用到哪些寄存器?
先说第一个问题,这个是很容易的,大家都知道C语言中是用栈来保存函数调用时的参数的
下面再分析其他几个问题:
在__cdcel标准中规定:
1、函数调用的参数入栈顺序是从右到左,即先压栈最右边的参数,最后压栈最左边的参数
2、在函数调用结束后用调用者来清理栈
3、调用过程中一般会用到eax、ebx、ecx等寄存器
void func(int a, int b)
{
...
}
int main(void)
{
...
func(1,2);
...
}
下面以这个例子来说明:
在调用的过程中由于是从右到左入栈,所以压栈的顺序是先2后1,用汇编可以描述为:
push 2
push 1
main函数对应的汇编大概是:
push 2
push 1
call func
add esp, 8 //清理栈,因为在调用过程中esp指针向下(或者说是向低地址)移动了8个字节
ret
这是调用顺序上
基于上面的几点我们就可以理解C语言中是如何实现变参函数的调用了:
1、由于函数调用是从右到左入栈的,因此函数调用时最左边的参数是最后一个入栈的,这样就可以函数调用时最左边的参数位置了
2、由于有format的指定,每个参数的大小是可知的,例如%d表示是int型,占用4个字节等
3、根据上面的两项就可以取出函数参数了。首先根据最左边参数(此处的含义是最后一个确定参数,也就是该参数后面的参数都是可变参数)
(例如
void func(char *arg1, char *arg2, ...)
那么最左边的参数意思就是arg2,因为它是最后一个确定参数
)
的位置可以找到函数调用时最左边的参数,然后根据每个参数的大小,移动esp指针(一般是向高地址移动),就可以依次取出其他所有参数了
wiki上介绍:http://en.wikipedia.org/wiki/X86_calling_conventions