在前期的学习中,我们可能会有很多困惑:
- 局部变量是怎么创建的?
- 为什么局部变量的值是随机值?
- 函数是怎么传参的?传参的顺序是怎样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?
- 函数调用结束后是怎么返回的?
那么通过学习函数栈帧的创建和销毁,以上困惑就会迎刃而解。
注: 本次讲解使用的是vs2013,不要使用太高级的编译器,越高级的编译器,越不容易学习和观察;同时,在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。
为了讲清楚函数栈帧,我们需要先做一些铺垫:
寄存器:
eax
ebx
ecx
edx
ebp
esp
ebp、esp这2个寄存器中存放的是地址,这2个地址是用来维护函数栈帧的
每一个函数调用,都要在栈区创建一个空间
接下来,就正式开始介绍函数栈帧的创建和销毁
- push ebp
- mov ebp,esp
- sub esp,0E4h
- push ebx
push esi
push edi
- lea edi,[ebp+FFFFFF1Ch] (显示符号名后可以看到就是[ebp-0E4h])
mov ecx,39h
mov eax,0CCCCCCCCh
rep stos dword ptr es: [edi]
把从edi这个位置开始,向下的39h个dword(double word;一个word是两个字节)的数据,全部改成CCCCCCCC
- mov dword ptr [ebp-8],0Ah
mov dword ptr [ebp-14h],14h
mov dword ptr [ebp-20h],0
- mov eax,dword ptr [ebp-14h]
push eax
mov ecx,dword ptr [ebp-8]
push ecx
- call 00C210E1
- push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
lea edi,[ebp+FFFFFF34h]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
- mov dword ptr [ebp-8],0
- mov eax,dword ptr [ebp+8]
add eax,dword ptr [ebp+0Ch]
mov dword ptr [ebp-8],eax
- mov eax,dword ptr [ebp-8]
把30放到eax这个寄存器里面
- pop edi
pop esi
pop ebx
- mov esp,ebp
pop ebp
- ret
- add esp,8
- mov dword ptr [ebp-20h],eax
main函数的销毁和Add函数的销毁类似,就不再进行演示了。
总结:
- 局部变量在函数的栈帧里被分配了一些空间进行创建
- 局部变量不初始化的时候是随机值(比如上述过程中不初始化之前是cccccccc)
- 函数在调用之前就把参数从右向左进行压栈;真正进入函数后通过指针的偏移量找到形参
- 形参是实参的一份临时拷贝,改变形参不会影响实参
- 通过寄存器把返回值带回来