当前位置:首页 » 《随便一记》 » 正文

C语言学习笔记—P17(函数栈帧的创建与销毁<超详解版>+图解+题例)_m0_57859086的博客

17 人参与  2022年05月23日 11:43  分类 : 《随便一记》  评论

点击全文阅读


目录

前言:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                      ——By 作者:新晓·故知

那些代码背后的故事:

通过反汇编讲解函数栈帧的创建与销毁!

 注:本次编译环境为:Visual Stdio 2013 !

 越高级的编译器越难以抽离函数栈帧分装的过程!

main函数也会被其他函数调用哦!

                                                                 

                                                                    

反汇编查看:​

 为a、b、c开辟空间并存值:​

                                                           ​

                                                                     ​

                                                                      ——By 作者:新晓·故知  整理+创作​

                                                                    ​

完成a的开辟、存值:

 完成b的开辟、存值:​​

 完成c的开辟、存值:​

                                                                     ​

 push 压栈:

                                                                     ​

 传参:​

 call指令将下一条指令的地址拿出压栈!​

                                                                   ​

 为Add函数准备栈帧!​

                                                                      ——By 作者:新晓·故知  整理+创作​

                                              

形参对应的函数(子函数)计算:​

 注:

函数调用时没有创建形参,最初在调用函数时,通过call指令和其他指令,就将参数传过去了,将实参a、b通过push压栈在a'、b'。因为是在栈中进行,所以先传的是b(右面的参数),压入栈中,再传a(左面的参数),故参数从右向左传参!

当进入函数计算时,形参回头去找压栈时的空间的a、b对应的值!

因此说:形参是对实参的临时拷贝!改变形参的值不影响实参!

 返回:

 先在eax保存z的值,安全!

                                                            ​

                                                            ​

此时所有指令回到main函数!main函数的栈帧交给esp,ebp进行维护!Add函数的栈帧销毁!别担心!z的值在寄存器eax中存放,安全着呢!

                                                             ​

 这时显现出最初在栈顶存储call指令的下一条指令地址的作用:

在进行函数调用结束后回到主函数,并从call指令的下一条指令开始执行!

逻辑严谨!不仅要走的出去,也要回的来!

 esp执行至此,形参的空间均已释放归还给操作系统!

 而eax的值暂存的z=30!主函数进行打印输出!

解答:

1.局部变量的创建是首先为函数分配栈帧空间,栈帧空间初始化一部分空间后,再为局部变量分配一些空间!

2.局部变量不初始化的是随机值,因为创建的时候是随机放置的值!初始化后随机值被覆盖!

3.函数的传参是在未调用形参对应的函数(子函数)时通过push 操作将实参的值压栈,当真正进入形参函数时,在形参对应的函数(子函数)栈帧里通过指针的偏移量,找回形参,进行使用!

传参是从右向左传!

4.形参是在压栈时开辟的空间 ,它和实参只是在数值上相同的,空间上是独立的!

形参是实参的一份临时拷贝!改变形参的值不影响实参!

5.函数的调用见以上讲解!

6.函数调用的结果的返回:

调用之前将call指令的下一条指令的地址压入栈,记录存储,将ebp的调用的函数的上一个函数(此例为主函数ebp-main)的栈帧的ebp地址存储记录,当形参对应的函数(子函数)调用完返回时,弹出ebp就找到原始函数(上一个函数,此例为main函数)的ebp,而esp的指针移动返回时就能找到原始函数(此例为main函数)的栈帧的顶,回到原始函数(main函数)的栈帧空间,就可以跳转到已记录的call指令的下一条指令的地址,进行返回。

返回值是通过寄存器eax带回!

                                                              

注:

函数内部创建的静态变量是在全局变量空间开辟的!而以上介绍的是在栈区空间开辟的!

编译器会根据函数的不同,开辟合适的空间!

 1.a和b是不连续的,相隔的空间大小取决于不同的编译器!

2.函数的形参可以理解为:不在形参对应的函数(子函数)的栈帧里,而在主函数main拓展的栈帧里!

                                                                         ——Since 作者:新晓·故知  整理+创作



前言:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                      ——By 作者:新晓·故知

那些代码背后的故事:

通过反汇编讲解函数栈帧的创建与销毁!

 注:本次编译环境为:Visual Stdio 2013 !

VS2013运行结果会一闪而过
解决办法1:
设置项目属性
解决办法2:
system("pause");

 

 越高级的编译器越难以抽离函数栈帧分装的过程!

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

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	return 0;
}

main函数也会被其他函数调用哦!

                                                                 

 

 

 

 

  

                                                                    

反汇编查看:

 

 为a、b、c开辟空间并存值:

 

                                                           

 

 

 

                                                                     

 

 

                                                                      ——By 作者:新晓·故知  整理+创作​​​​​​​

 

 

 

 

   

                                                                    

 

完成a的开辟、存值:

 

 完成b的开辟、存值:

 完成c的开辟、存值:

 

                                                                     

 

 push 压栈:

 

 

  

                                                                     ​​​​​​​

 传参:

 

 call指令将下一条指令的地址拿出压栈!

 

                                                                   

 为Add函数准备栈帧!

 

                                                                      ——By 作者:新晓·故知  整理+创作​​​​​​​

 33h次:16进制的33h次

 

                                              

形参对应的函数(子函数)计算:

 

 

 注:

函数调用时没有创建形参,最初在调用函数时,通过call指令和其他指令,就将参数传过去了,将实参a、b通过push压栈在a'、b'。因为是在栈中进行,所以先传的是b(右面的参数),压入栈中,再传a(左面的参数),故参数从右向左传参!

当进入函数计算时,形参回头去找压栈时的空间的a、b对应的值!

因此说:形参是对实参的临时拷贝!改变形参的值不影响实参!

 

 返回:

 先在eax保存z的值,安全!

 

                                                            

 

 

                                                            

此时所有指令回到main函数!main函数的栈帧交给esp,ebp进行维护!Add函数的栈帧销毁!别担心!z的值在寄存器eax中存放,安全着呢!

                                                             

 这时显现出最初在栈顶存储call指令的下一条指令地址的作用:

在进行函数调用结束后回到主函数,并从call指令的下一条指令开始执行!

逻辑严谨!不仅要走的出去,也要回的来!

 esp执行至此,形参的空间均已释放归还给操作系统!

 

 而eax的值暂存的z=30!主函数进行打印输出!

解答:

1.局部变量的创建是首先为函数分配栈帧空间,栈帧空间初始化一部分空间后,再为局部变量分配一些空间!

2.局部变量不初始化的是随机值,因为创建的时候是随机放置的值!初始化后随机值被覆盖!

3.函数的传参是在未调用形参对应的函数(子函数)时通过push 操作将实参的值压栈,当真正进入形参函数时,在形参对应的函数(子函数)栈帧里通过指针的偏移量,找回形参,进行使用!

传参是从右向左传!

4.形参是在压栈时开辟的空间 ,它和实参只是在数值上相同的,空间上是独立的!

形参是实参的一份临时拷贝!改变形参的值不影响实参!

5.函数的调用见以上讲解!

6.函数调用的结果的返回:

调用之前将call指令的下一条指令的地址压入栈,记录存储,将ebp的调用的函数的上一个函数(此例为主函数ebp-main)的栈帧的ebp地址存储记录,当形参对应的函数(子函数)调用完返回时,弹出ebp就找到原始函数(上一个函数,此例为main函数)的ebp,而esp的指针移动返回时就能找到原始函数(此例为main函数)的栈帧的顶,回到原始函数(main函数)的栈帧空间,就可以跳转到已记录的call指令的下一条指令的地址,进行返回。

返回值是通过寄存器eax带回!

                                                              

注:

函数内部创建的静态变量是在全局变量空间开辟的!而以上介绍的是在栈区空间开辟的!

编译器会根据函数的不同,开辟合适的空间!

 1.a和b是不连续的,相隔的空间大小取决于不同的编译器!

2.函数的形参可以理解为:不在形参对应的函数(子函数)的栈帧里,而在主函数main拓展的栈帧里!

                                                                         ——Since 作者:新晓·故知  整理+创作


点击全文阅读


本文链接:http://zhangshiyu.com/post/40606.html

函数  指令  调用  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 网站收录 | 免费二级域名 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备12007198号-1