千家信息网

C语言函数栈帧如何创建和销毁

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,这篇文章主要为大家展示了"C语言函数栈帧如何创建和销毁",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"C语言函数栈帧如何创建和销毁"这篇文章吧。写在前面我们
千家信息网最后更新 2024年11月22日C语言函数栈帧如何创建和销毁

这篇文章主要为大家展示了"C语言函数栈帧如何创建和销毁",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"C语言函数栈帧如何创建和销毁"这篇文章吧。

    写在前面

    我们知道,每一次函数调用都需要在栈区上为其开辟一块空间,这块空间就叫做这个函数的栈帧。

    而栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

    这样我们就了解了寄存器ebp和寄存器esp中存放的是地址,这两个地址是用来维护函数栈帧的。比如:调用main函数, 我们为main函数分配栈帧空间, 那么栈帧维护如下:

    下面我们通过一段代码分析一下,函数栈帧创建和销毁的过程:(栈帧这部分内容在不同的编译器上实现存在差异, 但是思想大致都是一致的。本文是在vs2013编译器下实现的。)

    #include int Add(int x, int y){        int z = 0;        z = x + y;        return z;}int main(void){        int a = 10;        int b = 20;        int ret = 0;        ret = Add(a, b);//计算a+b        printf("%d\n", ret);        return 0;}

    我们在调试过程打开调用堆栈

    可以看出,main函数是在__tmainCRTStartup函数内部被调用的,而__tmainCRTStartup函数又是在mainCRTStartup函数内部调用的。

    为了能更加清楚的看到栈帧创建和销毁的过程,我们转到上面代码对应的反汇编代码:

    int main(void){009D3F40  push        ebp  //将edp压入栈帧009D3F41  mov         ebp,esp  //将esp的值赋给edp009D3F43  sub         esp,0E4h  //esp-0E4h009D3F49  push        ebx  009D3F4A  push        esi  009D3F4B  push        edi  009D3F4C  lea         edi,[ebp+FFFFFF1Ch]  009D3F52  mov         ecx,39h  009D3F57  mov         eax,0CCCCCCCCh  009D3F5C  rep stos    dword ptr es:[edi]          int a = 10;009D3F5E  mov         dword ptr [ebp-8],0Ah          int b = 20;009D3F65  mov         dword ptr [ebp-14h],14h          int ret = 0;009D3F6C  mov         dword ptr [ebp-20h],0          ret = Add(a, b);//计算a+b009D3F73  mov         eax,dword ptr [ebp-14h]  009D3F76  push        eax  009D3F77  mov         ecx,dword ptr [ebp-8]  009D3F7A  push        ecx  009D3F7B  call        009D11F9  009D3F80  add         esp,8  009D3F83  mov         dword ptr [ebp-20h],eax          printf("%d\n", ret);009D3F86  mov         esi,esp  009D3F88  mov         eax,dword ptr [ebp-20h]  009D3F8B  push        eax  009D3F8C  push        9D5860h  009D3F91  call        dword ptr ds:[009D9118h]  009D3F97  add         esp,8  009D3F9A  cmp         esi,esp  009D3F9C  call        009D1140          return 0;009D3FA1  xor         eax,eax  }009D3FA3  pop         edi  009D3FA4  pop         esi  009D3FA5  pop         ebx  009D3FA6  add         esp,0E4h  009D3FAC  cmp         ebp,esp  009D3FAE  call        009D1140  009D3FB3  mov         esp,ebp  009D3FB5  pop         ebp  009D3FB6  ret

    main函数的调用 main函数栈帧的创建

    经过刚才我们的理解,在准备调用main函数的时候,调用main函数的那个函数的栈帧已经开辟好了。

    然后将ebp压入栈帧,保存了指向栈底的ebp的地址,而此时esp指向新的栈顶位置;接着将esp的值赋给了ebp,产生了新的ebp;用esp减去一个16进制数0E4H(这里就是为main函数预开辟空间)。紧接着三个压栈指令,分别将ebx,esi,edi,压入栈帧。加载完有效地址以后,将为main函数预开辟空间全部初始化为0xCCCCCCCC。最后创建了三个局部变量a,b,ret并进行了初始化。

    Add函数的调用

    函数传参

    将b的值存入寄存器eax中,再将eax压入栈中;将a的值存入寄存器ecx中,再将将ecx压入栈中;这里看出参数是从右向左传递的。紧接着执行call指令,这里就是调用Add函数,同时将call指令的下一条指令的地址压入栈中,然后执行call指令的时候按F11 , 就进入了Add函数内部。

    Add函数栈帧的创建

    首先将main()函数的ebp压入栈,保存指向main()函数栈帧底部的ebp的地址,此时esp指向新的栈顶位置;将esp的值赋给ebp,产生新的ebp,即Add()函数栈帧的ebp;给esp减去一个16进制数0E4H,这里是为Add()函数预开辟空间;紧接着三个压栈指令,分别将ebx,esi,edi,压入栈帧。加载完有效地址以后,将为Add函数预开辟空间全部初始化0xCCCCCCCC。在紧接着创建了变量z,将形参的a和b相加的结果存储到z中;最后将结果存储到eax寄存器中,通过寄存器带回了函数的返回值。

    Add函数栈帧的销毁

    edi、esi、ebx依次出栈,esp 会向下移动;然后将ebp的值赋给esp,使esp指向ebp指向的地方;接着ebp 出栈,同时将出栈的内容给ebp,此时ebp又指向了main函数栈帧的底部,最后执行ret 指令,表示出栈一次,并跳转到出栈的内容的地址处,也就是call指令的下一条指令处。

    main函数栈帧的销毁

    main函数栈帧的销毁和Add函数栈帧销毁的过程的思想都是一样的,这里就不做多赘述了。

    以上是"C语言函数栈帧如何创建和销毁"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

    0