[C语言]栈帧和函数调用的关系? 用最直白的语言解释一下.

编程入门 行业动态 更新时间:2024-10-26 22:25:05

[C<a href=https://www.elefans.com/category/jswz/34/1770116.html style=语言]栈帧和函数调用的关系? 用最直白的语言解释一下."/>

[C语言]栈帧和函数调用的关系? 用最直白的语言解释一下.

先来看一段超级简单的代码

# include<stdio.h>
int myadd(int x,int y)
{int z=x+y;return z;
}
int main()
{int a=0XAAAAAAAA;int b=0xBBBBBBBB;int c=myadd(a,b);printf("main:you would   run here!\n");printf("res : %d\n",c);getchar();return 0;
}

在main函数中创建了两个临时变量a,b然后调用myadd函数进行两数相加,在返回他们的和.从字面代码来说,里面的逻辑懂编程的人肯定都能理解.
但是在内存中
main函数和myadd函数是什么样子呢?
main函数是通过什么方式调用myadd函数的呢?
main里的临时变量又是如何被myadd函数使用的呢?
myadd的返回值又是如何传递给main函数的呢?
让我们带着疑问,走进今天的瞎逼课堂.

何为栈帧

要想清楚的了解上述问题,首先我们得认识一个结构,叫栈帧(StackFrame).我来给大家画一个栈帧长什么样.

毕竟小时候没有像达芬奇一样画过鸡蛋,所以绘图水平…..凑活着看吧
栈帧,其实就是一个栈结构,不过这个栈特别之处是就是:从高地址往低地址增长的 (脑门朝下生长).
栈底地址比栈顶地址定高.
其中两个关键的寄存器是EBP(Extended Base Pointer)和ESP(Extended Stack Pointer )
这两个是指针寄存器,说白了就是俩指针,EBP指向栈底,ESP指向栈顶.
有了这两个指针就能很方便的对栈帧进行操作了.
其中基本的操作就是push和pop了
(友情提示 :
push的时候ESP往下移动,减地址
pop 的时候ESP往上移动,加地址
)
好了,到这里我们基本就知道是栈帧长啥样了,我们要知道,每一个函数都在内存中会开辟一段栈帧.
接下来,就是见证main函数栈帧的时刻.

main函数的栈帧

main函数也是函数,所以也有相应的栈帧,这里我们用反汇编来看一下main函数初始化对应的汇编代码

我们只关心前三行代码,前三行已经完成一个栈帧开辟工作,其他的代码对我们理解栈帧没有太多帮助.
用大白话翻译如下:

啪! 把ebp(里面保存了main函数的地址)抓住压入了栈中,(push ebp)
嗖!esp为了救出ebp也飞快的来到了ebp这里(mov ebp esp)
嘭!esp一不小心又往下掉了50H米 (sub esp,50H)
ebp往下望了望说:”你凉了”

此时ebp和esp相距的距离就是这个栈帧的大小,
如图所示

main函数初始化A和B

有了main函数的栈帧,就可以在其内部操作一番了,今天我们的操作是初始化两个临时变量A和B(最开始代码里面的)
我们看看在汇编里是如何初始化的

翻译成大白话如下

a说:”EBP是老大,我就是一人之下万人上的老二,所以我只应该在EBP下面” (mov dword ptr [ebp-4],a)

(友情小剧场).a初始化是整型,占四个字节,所以一个a占有4个内存单元,所以a应该在ebp-4的位置(a这个指针指向最小的单元,依次往上4个单元都是a的内容)

b对a说:”你牛逼你第一,你在海离开飞机“b的确争不过a,只能排在a后面,所以在ebp看来他是第三名.(mov dword ptr [ebp-8],b)

调用myadd函数之前的准备工作

来看一下相应汇编

翻译成大白话如下

a对b说:”唉呀妈呀,好像myadd函数叫咱两过去一趟”
b说”这老大远滴,咋过去呢.”
a说”我海里开飞机,来, 一块儿把你给载上”
b说:”那可不行,你那地儿忒窄,坐一起不舒服”
a说 “那你咋地过去”
b说” 我天上开坦克啊”.
one minute later
call myadd的 “你在哪呢,我们到了”

其中 mov eax,dword ptr[ebp-8] 就是a坐飞机(eax)
push eax 目的地到了 停飞机

mov ecx,dword ptr[ebp-8] 就是b坐坦克,(ecx)
push ecx 目的地到了 停坦克.
call xxxxxxx
拨打my add 的手机号XXXXXXXX
(并反手定了回去的机票)

开辟myadd的栈帧


同样只关心前三行代码,
push ebp 将main函数的地址压入栈
mov ebp esp esp指向ebp
sub esp 44H esp向下移动44H个内存单元


这图片描述的是形参实例化和开辟myadd函数的总过程

在myadd函数中操作一番

别忘了myadd函数作用是将a和b联谊,相亲相爱产生结果c并送回main函数
来看一下源码

翻译成大白话如下

顺着ebp啪啪啪往上走了8个字节,找到了A,然后把A丢进飞机里(eax)
顺着ebp啪啪啪啪往上走了12个字节,找到了B,把B也扔进了飞机里
b说:”你这飞机里太挤了”
a说”挤就挤挤呗”
于是a和b在飞机里挤了挤,挤出了结果留在eax中.
咚咚咚顺着ebp往下走了4个字节,创建了c,把eax里的结果给c
还有一点 如果要把 c返回(return z),也要依靠 eax这个交通工具,把值存在eax中返回

myadd函数的返回

myadd函数执行完之后,就要返回到main函数,怎么返回呢?
我们来看一下汇编代码

前三行是因为myadd开辟时将 ebx esi edi push入栈了所以要按顺序一次出栈.我们可以忽略不记.
看第四行!!!!
首先 将ebp的地址给esp(esp指向了ebp)这时ebp和esp又在一起了
ebp esp一旦碰在一起,就表明myadd这个函数的栈帧消失了!


接下来 pop ebp(对照着图片)
pop操作有两个步骤
1. 将main函数的地址给ebp, 这时候ebp就指向了main函数的地址,实现了函数的返回.
2. esp-4, esp指向了如图所示的地方

来看最后一行 汇编 ret
ret的意思可以解释为
1. 将esp指向的内容给EIP寄存器
(EIP寄存器里存的是整个程序将要执行的下一条代码的地址)
这样整个程序就知道执行完myadd函数之后要做什么了.
2.将esp指向main函数开辟栈帧的地方
ebp里和esp里内容就回到调用myadd函数之前的位置了
如图所示
到这里就完成了对myadd函数的调用

全文完

跑路.

更多推荐

[C语言]栈帧和函数调用的关系? 用最直白的语言解释一下.

本文发布于:2024-03-07 11:18:25,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1717680.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:语言   直白   函数   关系

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!