温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2022-03-04 09:53:42 来源:亿速云 阅读:174 作者:小新 栏目:开发技术

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

    写在前面

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

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

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

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

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

    #include <stdio.h>
    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;
    }

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

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

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

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

    int main(void)
    {
    009D3F40  push        ebp  //将edp压入栈帧
    009D3F41  mov         ebp,esp  //将esp的值赋给edp
    009D3F43  sub         esp,0E4h  //esp-0E4h
    009D3F49  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+b
    009D3F73  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函数的那个函数的栈帧已经开辟好了。

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

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

    Add函数的调用

    函数传参

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

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

    Add函数栈帧的创建

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

    首先将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函数栈帧的销毁

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

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

    main函数栈帧的销毁

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

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

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

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI