一. 函数参数传递形式
函数的参数传递有2种方式:堆栈方式
、寄存器方式
。
如果是堆栈方式传递的,就需要定义函数参数在堆栈中的传递顺序,并约定函数被调用之后,由谁来平衡堆栈;
如果是寄存器方式传递的,就需要确定参数存放在哪个寄存器中。
每一种方式都有其优缺点,而且与使用的编程语言有关系,不存在哪种方式好与坏。
我们在开发中经常遇到调用约定类型
,如__cdecl
、stdcall
、PASCAL
、fastcall
。这些调用约定类型就用来指定函数参数的传递方式的。上面几种约定类型,除了fastcall
是使用寄存器方式传递参数外,其他的都是使用堆栈传递参数的。
Visual Studio中的C++工程,可以
C++
–>高级
–>调用约定
中进行调用约定的设置:
二. 使用堆栈方式传递函数参数
堆栈是一种“后进先出”的数据结构,ESP
寄存器始终指向栈顶。栈中数据地址从底部到顶部依次减小,也就是说,栈底对应高地址,栈顶对应低地址。
调用函数时,调用者依次把参数压栈,然后调用函数,函数被调用之后,在堆栈中取得参数数据。函数调用结束以后,堆栈需要恢复到函数调用之前的样子,而到底是由调用者来恢复还是由函数自身来恢复,根据不同的调用约定类型采用不同的方式。
约定类型 | __cdecl | stdcall | PASCAL | fastcall |
---|---|---|---|---|
参数传递顺序 | 从右到左 | 从右到左 | 从左到右 | 使用寄存器 |
堆栈平衡者 | 调用者 | 函数自身 | 函数自身 | 函数自身 |
__cdcel
是C/C++/MFC程序默认的调用约定。
stdcall
是绝大多数Win32 API函数的约定方式,也有少部分使用__cdcel
约定方式(如wsprintf等)。
在Windows C/C++开发中常用的就是__cdecl
和stdcall
这2种调用约定。
按照不同的调用约定
来调用函数int add(int a, int b)
。从调用者的视角来看,其汇编代码分别表示如下:
__cdecl
push b ;参数按从右到左传递
push a
call add
add esp, 8 ;调用者在函数外部平衡堆栈
stdcall
push b ;参数按从右到左传递
push a
call add ;函数自己内部平衡堆栈,调用者不需要平衡堆栈
在函数调用过程中,参数入栈的过程如图:
上图中,EBP
和函数返回地址ret
都是32位地址。因为函数调用完之后会将EBP
恢复为暂存在堆栈中的原EBP
值,所以从调用者角度来看,在函数的一次调用过程中EBP
是不会变化的。
我们可以在函数中通过新的EBP
获取函数各个参数的值:
参数a = EBP + 0x8
参数b = EBP + 0xC