栈作为重要的数据结构,主要用于管理程序的执行过程中的函数调用和局部变量。它通过后进先出的原则存储和管理数据,包括函数的参数、返回地址、局部变量等。
重要概念
栈
栈在数据结构中是一种运算受限的线性表,即我们只能对表尾进行操作,称之为栈顶,相对的,另一端称为栈底。它按照先进后出的原则存储数据,先进入的数据保存在栈底,后来的数据保存在栈顶。栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,存储了函数调用时所需要使用的信息。
EBP
栈底指针
ESP
栈顶指针
call
汇编语言,调用函数时需要使用call来调用
retn
函数调用结束后返回。当retn指令执行时是将esp指向地址的值弹出到EIP寄存器中,表示返回到调用函数的下一条指令。
开辟栈帧
1 | ;栈帧结构 |
函数调用约定
在栈帧的开辟中涉及到函数的调用。例如有一个函数
1 | int test(int a,int b); |
编写代码时我们肯定理解这个函数如何传参,传几个参数。但是CPU怎么才能知道这个函数有几个参数,参数的值是什么,为了解决这些问题,计算机提供了一种被称为栈的数据结构来支持参数传递。
但是还有问题没有解决,当函数传递参数时,按照什么样的顺序传参,是从左到右还是从右到左,调用结束后如何清除堆栈,由调用者清除还是被调用者清除以及寄存器如何使用。这时就提出了函数调用约定来解决这个问题
stdcall
声明的语法为:
1 | int __stdcall function(int a,int b); |
stdcall的调用约定意味着:
- 参数从右向左压入堆栈
- 函数自身清理堆栈
- 函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
cdecl
是C和C++默认的调用约定
声明的语法:
1 | int function (int a ,int b); //不加修饰就是C调用约定 |
cdecl调用约定意味着:
- 参数从右向左压入堆栈
- 调用者负责清理堆栈
- C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。
- 仅在函数名前加上一个下划线前缀,格式为_functionname。
fastcall
声明语法:
1 | int fastcall function(int a,int b); |
fastcall调用约定意味着:
- 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈
- 函数自身清理堆栈
- 函数名修改规则同stdcall:函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
thiscall
C++类成员函数缺省的调用约定
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:
- 参数从右向左入栈
- 如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。如果参数个数确定,this指针通过ecx传递给被调用者。
- 如果参数个数不确定,调用者清理堆栈,否则函数自己清理堆栈
对于参数个数固定情况下,类似于stdcall,不定时则类似cdecl
naked call
一个很少见的调用约定,一般用于实模式驱动程序设计