函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。 在参数传递过程中需要解决两个问题:
- 当参数大于一个时,参数的入栈顺序如何,即:从右向左亦或是由右向左。
- 恢复堆栈的任务是由调用函数完成,还是被调用者负责。
这就是函数调用约定所需要解决的问题,在高级语言中,通常存在以下几种调用约定:
- stdcall
- cdecl
- fastcall
- thiscall
- naked call
stdcall的调用约定
stdcall很多时候被称为pascal调用约定,因为pascal 是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。stdcall的调用约定意味着:
- 参数从右向左压入堆栈,
- 函数自身修改堆栈
- 函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
对于下面的一段代码:
1 2 3 4 5 6 7 8 9 10 | int __stdcall function(int a, int b) { return a + b; } int main() { int result = function(1, 2); return 0; } |
用g++生成相应的汇编代码:
$ g++ test.cpp -o test.s -S |
汇编代码(部分核心代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | _function__Fii@8: ;函数名 pushl %ebp ;保存堆栈指针 movl %esp,%ebp movl 8(%ebp),%eax ;参数int a movl 12(%ebp),%ecx ;参数int b leal (%ecx,%eax),%edx ;计算a + b并保存至寄存器edx movl %edx,%eax ;将结果移至寄存器eax,即返回值 movl %ebp,%esp popl %ebp ret $8 ;又被调用函数负责恢复堆栈 _main: pushl %ebp movl %esp,%ebp subl $16,%esp call ___main pushl $2 ;压入参数int b pushl $1 ;压入参数int a call _function__Fii@8 ;调用函数function movl %eax,%eax movl %eax,-4(%ebp) xorl %eax,%eax xorl %eax,%eax movl %ebp,%esp popl %ebp ret |
cdecl调用约定
默认情况下,g++采用这种约定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | _function__Fii: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax movl 12(%ebp),%ecx leal (%ecx,%eax),%edx movl %edx,%eax movl %ebp,%esp popl %ebp ret _main: pushl %ebp movl %esp,%ebp subl $16,%esp call ___main pushl $2 pushl $1 call _function__Fii addl $8,%esp ;由调用者负责清理堆栈 movl %eax,%eax movl %eax,-4(%ebp) xorl %eax,%eax movl %ebp,%esp popl %ebp ret |
未完待续……
你好!除了代码,此处没有多少原创之物,皆为本人搜集、整理、总结之记录与心得,欢迎转载分享!转载时请尽量注明出处,将不胜感激。祝你健康、快乐!
Be the first to comment on this entry.