深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2

简介: 深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2

深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1

https://developer.aliyun.com/article/1456960



3.main函数中的核心代码

接下来执行以下三句代码:

b1b75cba216a4e70b08a80c0d0c2d818.png



以a为例子,观察下图:


0d18fa0482484a3aaa218283e26effe1.png


       可得出以下图解:

533b99114b434fe79df2f661d92ff5a8.png



然后接下来执行以下指令:


38541978f4d94174b2664db20ad2934e.png


首先来看前两条指令:


d3b6e64e78b5410f8b04c11f2a776e05.png



mov 是一个指令,用于将数据从一个位置复制到另一个位置。
eax 是一个32位的寄存器,用于存储通用数据。
dword ptr 是一个修饰符,用于指示后面的操作数应该被视为32位的双字(即4个字节)。
[ebp-14h] 是一个内存引用,它指向位于基址指针 ebp 减去 14h(20个字节)的位置。基址指针 ebp 是一个用于存储局部变量和函数参数的寄存器。
综上所述,这行代码的作用是将位于 ebp-14h 地址处的32位数据加载到 eax 寄存器中



159c0ed768bf434fab4fae5a2c5afddd.png


push 是一个指令,用于将数据压入堆栈中。
eax 是之前加载了数据的寄存器。

综上所述,这行代码的作用是将 eax 寄存器中的值(20)压入堆栈中


所以,后两条指令同理③④

af3c6326d6654caaa1101c728b6f57d6.png



       将位于 ebp-8 地址处的32位数据加载到 ecx 寄存器中,将 ecx 寄存器中的值(10)压入堆栈中


图解:


892e812cd0ae4a11b6924d0f8f2c0b99.png


🧨call指令🚩

函数调用过程


8fd5e41c2fd14d61993352c42895ffc1.png


call 是一个指令,用于调用一个函数或子程序。它的作用是将当前指令的下一条指令的地址(返回地址)压入堆栈,并跳转到指定的函数或子程序的地址执行。

按f11,通过call指令就会进入Add函数里面去了(并未真正进入,还要再按一次f11)


1f472cc11e3949bcbef980d51ca8821d.png


    call 指令是要执行函数调用逻辑的,在执行call指令之前先会把call指令的下一条指令的地址进行压栈操作,这个操作是为了解决当函数调用结束后要回到call指令的下一条指令的地方,继续往后执行


因此,call  的作用是将当前指令的下一条指令的地址压入堆栈(00C21450),并跳转到地址为 00C210E1 的函数或子程序的入口点执行



3e6752a31442448f86e792a64ed5a84d.png

4.Add函数栈帧的创建

       再按下f11,这时候才是真正来到add函数内,前面那一堆汇编代码跟main函数栈帧创建逻辑是一样的。


反汇编代码:


fb296a6440b34f2bac8fb6c070e3a46c.png


前提说明


d4086e6b28d2490dbd70035ff55a44d1.png


图解:


001d7c9cf51b4dd7af0382d0db6e339a.png


5.Add函数中的核心代码🎯

反汇编代码:


bb982ceeb5734345b485afeeb73d14fd.png

①:将值0(初始化z)存储到位于内存中的地址 ebp-8 处的双字(32位)数据中。


②将位于内存中地址 ebp+8 处的双字(32位)数据(当前位置的值为10)加载到寄存器 eax 中。


③将位于内存中地址 ebp+0Ch 处的双字(32位)数据(当前位置的值为20)与寄存器 eax 中的值相加,并将结果存储(两数相加的结果为30)回 eax 寄存器中。


④位于当前堆栈帧中相对于基址寄存器 ebp 偏移 8 字节的内存位置的值(当前值为30)复制到寄存器 eax 中


图解:

6ba2e63080274818ba925e1c2d915e6d.png



6.Add函数栈帧的销毁

代码:

58fc6049c05e4c7796d1c0f591608e3c.png



这句代码的意思是:


       将位于 ebp-8 地址处的32位数据(值为30)加载到寄存器 eax 中,因为函数出去之后,值就销毁了,但是如果放在寄存器eax内就安全了,相当于用了一个全局的寄存器把返回值保存起来,回到主函数main再用。


fb861b32265c4f3e9eaa5c7d14579cab.png


然后pop三次,把三个寄存器的地址分别弹出:


8934740f1f4e4076ba80841c2c89d956.png


接着 mov esp,ebp,就是把ebp当前地址赋值给esp:

1612ed4d17914945ade38882beb6b710.png



接着pop ebp,此时ebp回到main函数函数栈帧的栈底:


726a36a855f149efb5f6885bab36ae48.png


       说明此时Add函数已经销毁了。


此时最重要的一条指令来了:


       当pop ebp之后,只是让我们找到了esp和ebp的栈帧空间,但是当我回到main函数的时候,还应该从call指令的下一调指令的地址开始执行,所以此时恰好栈顶上就放着这个地址



deff6fc4ac274528aeec0168255d5bb0.png

       这个ret指令return返回的时候,这个指令其实就是从栈顶弹出了call指令下一条指令的地址,然后跳那去了,接着F10走一下,回来main函数内:


258b3e7b0ab048e2aad7f05393154e5d.png


       存这个地址(00C21450)就是当函数调用完之后还能回来,从call指令的下一条指令的地址开始执行。


所以图解是这样的:



02c7b2a5b5114eb2a56c1e85101f272e.png

关于形参变量空间的释放:


358228cab60d4bf5b9beb90ef22dfa90.png




返回值是怎么带回来:先把值委托到eax寄存器内,接着回到main函数内部赋值

bafde95d8640464aa5f034eacad51b4c.png





       经过esp+8之后,关于x和y两个形参空间的变量就已经销毁,还给操作系统了。


关于main函数的销毁跟上述Add函数的销毁逻辑相似,也不累赘地列举了。

总结:

1.局部变量是如何创建的?


     

首先为main函数分配栈帧空间,然后在栈帧空间内初始化一部分空间之后,给局部变量在该栈帧空间内分配一点空间

2.为什么局部变量不初始化内容是随机的?


 

因为随机值是我们放进去的,如果局部变量给它们初始化,那就是把随机值覆盖了。

3.函数调用时参数时如何传递的?传参的顺序是怎样的?


   

当我要调用那个函数的的时候,就已经push,push,把这两个参数从右向左开始压栈压进去,当我们进入形参函数Add的时候通过指针的偏移量找回来找到了形参


4.形参和实参的关系是什么呢?


     

形参确实是我在压栈的时候开辟的空间,形参和实参只是值是相同的,空间是独立的,所以形参是实参的一份临时拷贝,改变形参不会影响实参

5.函数调用结束后是如何返回的?

我们在调用之前就已经把call指令的下一条指令的地址压栈压进去了,当函数调用完要返回的时候,弹出ebp就能找到原始上一个函数调用的ebp,然后指针往下走的时候就能中找到esp的地址,接着跳转到call指令下一条指令的地址,返回值是通过寄存器的方式带回来的

     

相关文章
|
19天前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
|
2月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
87 1
|
2月前
|
NoSQL 编译器 程序员
【C语言】揭秘GCC:从平凡到卓越的编译艺术,一场代码与效率的激情碰撞,探索那些不为人知的秘密武器,让你的程序瞬间提速百倍!
【8月更文挑战第20天】GCC,GNU Compiler Collection,是GNU项目中的开源编译器集合,支持C、C++等多种语言。作为C语言程序员的重要工具,GCC具备跨平台性、高度可配置性及丰富的优化选项等特点。通过简单示例,如编译“Hello, GCC!”程序 (`gcc -o hello hello.c`),展示了GCC的基础用法及不同优化级别(`-O0`, `-O1`, `-O3`)对性能的影响。GCC还支持生成调试信息(`-g`),便于使用GDB等工具进行调试。尽管有如Microsoft Visual C++、Clang等竞品,GCC仍因其灵活性和强大的功能被广泛采用。
108 1
|
2月前
|
编译器 C语言 计算机视觉
C语言实现的图像处理程序
C语言实现的图像处理程序
110 0
|
11天前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
1月前
|
存储 编译器 程序员
C语言程序的基本结构
C语言程序的基本结构包括:1)预处理指令,如 `#include` 和 `#define`;2)主函数 `main()`,程序从这里开始执行;3)函数声明与定义,执行特定任务的代码块;4)变量声明与初始化,用于存储数据;5)语句和表达式,构成程序基本执行单位;6)注释,解释代码功能。示例代码展示了这些组成部分的应用。
71 10
|
2月前
|
存储 C语言
【C语言】——函数栈帧的创建与销毁
【C语言】——函数栈帧的创建与销毁
|
2月前
|
自然语言处理 编译器 C语言
C语言程序的编译
C语言程序的编译
53 2
|
3月前
|
前端开发 C语言 C++
C语言03----第一个程序HelloWorld(vs版)
C语言03----第一个程序HelloWorld(vs版)
|
4月前
|
自然语言处理 C语言 C++
程序与技术分享:C++写一个简单的解析器(分析C语言)
程序与技术分享:C++写一个简单的解析器(分析C语言)