您的位置:首页 > 编程语言 > C语言/C++

*函数的调用——栈帧*

2017-12-09 23:01 330 查看

1.函数调用

首先函数的存在是为了使代码模块化,逻辑清晰且便于查错、进行二次开发。那每一次的函数调用都是一个过程,这个过程我们称为:函数的调用过程。这个过程要为函数开辟栈空间,用于该函数调用中的临时变量的保存、现场保护,这块栈空间即为函数栈帧。

2.栈帧

编写如下代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <Windows.h>
#include <stdio.h>

int add(int x, int y)
{
int z = x + y;
return z;
}

int main()
{
int a = 15;
int b = 20;
int c = add(a, b);
printf("%d", c);
system("pause");
return 0;
}


调试程序并进入反汇编有:

00931410  push        ebp
00931411  mov         ebp,esp
00931413  sub         esp,0E4h
00931419  push        ebx
0093141A  push        esi
0093141B  push        edi
0093141C  lea         edi,[ebp-0E4h]
00931422  mov         ecx,39h
00931427  mov         eax,0CCCCCCCCh
0093142C  rep stos    dword ptr es:[edi]


     在程序执行时,main函数是程序的入口,但是_main函数不是第一个被调用的函数,第一个被调用的函数是_mainStartup函数,是它调用了main函数。ebp、esp是两个基址寄存器,分别用来存放函数栈帧栈底的地址、函数栈帧栈顶的地址。

int a = 15;
0093142E  mov         dword ptr [ebp-4],0Fh
int b = 20;
00931435  mov         dword ptr [ebp-8],14h


     此时,_main函数被调用,同时为main函数创建了栈帧。将15即a放入ebp-4的地址,将20即b放入ebp-8的地址。此时的栈帧图如下:



int c = add(a, b);
0093143C  mov         eax,dword ptr [ebp-8]
0093143F  push        eax
00931440  mov         ecx,dword ptr [ebp-4]
00931443  push        ecx
00931444  call        @ILT+0(_add) (09310EBh)
00931449  add         esp,8

     将b放入eax寄存器中,且将eax压入栈中;将a放入ecx寄存器中,且将ecx压入栈中

     call指令的功能:将当前正在执行指令的下一条指令地址压入栈中;随机跳(jmp)至指定函数

     所以将地址00931449压入栈中(压栈操作同时伴随栈顶指针的改变),同时跳转至add函数,此时栈帧图如下:



int add(int x, int y)
{
009313D0  push        ebp
009313D1  mov         ebp,esp
009313D3  sub         esp,0CCh
009313D9  push        ebx
009313DA  push        esi
009313DB  push        edi
009313DC  lea         edi,[ebp-0CCh]
009313E2  mov         ecx,33h
009313E7  mov         eax,0CCCCCCCCh
009313EC  rep stos    dword ptr es:[edi]
int z = x + y;
009313EE  mov         eax,dword ptr [ebp+8]
009313F1  add         eax,dword ptr [ebp+0Ch]
009313F4  mov         dword ptr [ebp-4],eax

     此时函数add的栈帧也被创建,首先将寄存器ebp中的内容压入栈中,即将main函数的ebp中的内容压入栈中;再将寄存器esp中的内容放入寄存器ebp中,即栈底指针下移至栈顶指针处;再将esp减去0CCh,即栈顶指针下移,此时ebp、esp分别指向add函数的栈底和栈顶;再将ebp+8即a的内容放入eax中;再将a加上 ebp+0Ch处的b放入eax中;再将 eax的内容放入ebp-4中,此时栈帧图如下:



return z;
009313F7  mov         eax,dword ptr [ebp-4]
}
009313FA  pop         edi
009313FB  pop         esi
009313FC  pop         ebx
009313FD  mov         esp,ebp
009313FF  pop         ebp
00931400  ret

     再将 ebp-4的内容放入eax中;将ebp的内容给esp,即将栈顶指针上移至栈底指针处(此时函数add的栈帧被销毁);pop将栈顶弹出且放入ebp中,即将ebp改变;ret的功能:将栈顶的值弹出,且将弹出的值放入eip,此时返回到main函数里。此时栈帧图如下:



00931449  add         esp,8
0093144C  mov         dword ptr [c],eax

      此时给esp加8,即将栈顶上移;将eax的内容放入 ebp-0Ch位置,即将z=a+b放入到了main函数的栈帧中,此时整个函数调用过程完成。此时栈帧图如下:



最终运行结果为:




                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息