| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 【C语言进阶】函数栈帧的创建和销毁(内功修炼) -> 正文阅读 |
|
[C++知识库]【C语言进阶】函数栈帧的创建和销毁(内功修炼) |
目录 2.1 _tmainCRTStartup函数(调用main函数)栈帧的创建 前言??在前期的学习过程中,我们可能会有很多的困惑:
这里使用的环境是 Visual Studio 2019(原本想用 Visual Studio 2013?的,但是没有安装有),提示不要使用太过高级的编译器,因为越高级的编译器越不容易观察。同时这里需要注意的是在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,不是完全相同的,具体细节取决于编译器? 一、基础知识1.1 什么是栈区?
今天文章的内容是关于栈区的,其他简单了解即可? 接下来,补充一下栈的知识,了解到这就可以了,足够使用了
接下来还要了解一个重要的东西,寄存器,寄存器整篇文章都在使用。 ?-------------------我是分割线------------------ 1.2 寄存器这里简单介绍一些寄存器,其它的先不要过多理解 常见寄存器有eax、ebx、ecx、edx,这四个都当做通用寄存器,保留临时数据,ebp和esp较为特殊
1.3 测试代码和一些其它的使用的测试代码(足够简单才好演示):
接下来还有一些汇编代码的含义:
测试编译器使用的是 VS2019,以调试一步步进行演示? 准备工作已经做好,接下来开始演示。 ?-------------------我是分割线------------------ 二、函数栈帧的创建和销毁的过程每一个函数调用,都要在栈区创建一个空间 2.1 _tmainCRTStartup函数(调用main函数)栈帧的创建先了解 main 函数是被谁调用的,按 F10 或 F11?进入调试模式,F10是逐过程,F11是逐语句,打开堆栈 这时看到调用堆栈这个窗口 按F10,按到return 0 时再按一次,调用栈堆会出现以下内容(我使用的VS2019 没有出现,可能编译器版本太高,优化掉了)? 这时再看堆栈窗口发现 main 函数被 __tmainCRTStartup() 调用 而 __tmainCRTStartup() 又被 mainCRTStartup() 调用 观察C语言代码所对应的汇编代码,在调试状态下,右击鼠标转到反汇编 ?转到汇编后,右键 取消符号名,方便查看阅读汇编代码 先看以下汇编代码? ? ?编译器会先在栈区处开辟一部分空间给? __tmainCRTStartup()? 和? 2.2?main函数栈帧的创建先看第一部分汇编代码,逐条语句解释
?此时进入main函数(也就是程序调试开始),首先要 push ebp 进行压栈,ebp?在 __tmainCRTStartup() 上面压栈 观察esp ebp 地址的变化,在调试的监视里面查看,push ebp 之后,esp 指向的位置也随之改变 (地址减小) ? 接下来是 mov ?ebp,esp? ,将esp的值传入ebp中(即将ebp指针移动到esp指向的位置) ?接下来 sub ?esp,0E4h,将esp的内容减去0E4h(将esp移动到原esp-0E4h的位置,esp-0E4h地址减小) ? ? ? 接下来 push ?ebx? ,在栈顶放入ebx,地址依旧减小 ?接下来 push ?esi? ,在栈顶放入 esi,地址依旧减小 ? 接下来 push ?edi? ,在栈顶放入edi,地址依旧减小 步骤演示图: ?接下来:?
?lea ?edi,[ebp-24h],把 ebp - 0E4h 这个地址加载到 edi 里 顺便看一下,ebp-24h 的地址? 接下来 mov ?ecx,9,将9放入ecx
?这三步执行完,把 edi 这个位置开始向下的 9 行?dword 数据全部改为 0xcccccccc (word是2个字节,dword是4个字节),一共36个字节,一行四字节,共9 行 ?调试里打开内存监控,内存监控中的内存地址也是向上减小的 ?步骤演示图: 到这main函数的函数栈帧已经创建好了 2.3?main函数内执行有效代码接下来开始初始化 a、b、c 局部变量,直接看mov初始化,上面两行不用理
mov ? ? dword ptr [ebp-8],0Ah? ,把 0Ah(十进制为10) 放到 ebp-8 的位置 ?mov ?dword ptr [ebp-14h],14h? ,把 14h(20) 放到 ebp-14h的位置 mov ?dword ptr [ebp-20h],0? ,把 0 放到 ebp-20h的位置? 到这里a,b,c 已经初始化完成了 步骤演示图: 接下来:?
??mov ?eax,dword ptr [ebp-14h]? ,把 ebp-14h 的值0000 0014(十进制是20)放到 eax 里去 ?push ?eax? ,压栈 eax(20),esp指向的位置也随之改变 (地址减小)? ??mov ? ecx,dword ptr [ebp-8]? ,把 ebp-8 的值0000000a(十进制是10)?放到 ecx 里去 ?push ?ecx? ,压栈 ecx(10),esp指向的位置也随之改变 (地址减小)? ?步骤演示图:(截一部分,用不到的先不截) ?接下来为call 指令,按下F11,此时就正式进入Add函数内部 并为其开辟栈帧,详情见下文 2.4 Add函数栈帧的创建按 F11,进入到 Add 函数?,该add 函数地址不一定与main 函数地址相连,但是add 函数的地址一定在main 函数地址上面
?call 指令调用 Add 函数,这里逐语句(F11)执行,发现这里竟然存储着下一条指令的地址,事实上 call 指令把下一条指令的地址压栈了(为了 Add 函数结束后能找回来),esp 地址也跟着变化 ?进入 Add 函数前,会先为 Add 函数开辟函数栈帧,这这些操作跟先前main函数开辟函数栈帧操作一样,所以这里就不细谈了
首先,push ebp把ebp压栈到栈顶,再mov把esp赋给ebp,再sub,把esp-去0CCh,此步骤就是在为Add函数开辟空间,接着进行三次push,同main函数那样,同理,依旧是赋值为CCCCCCCC,详细过程不再赘述,跟上文main函数一样,如图所示: 详细: 简图: 2.5 Add函数内执行有效代码?
首先,把0放到ebp-8的位置上,接着mov把ebp+8的值放到eax里头去,此时eax就是10。再add给eax加上ebp+0ch,就是把20加进去,此时eax就是30,加完后再把eax(30)放到ebp-8里头去,最终的结果(30)放到z里头去。 ?接下来就要进行返回了,也就是Add函数栈帧的销毁,见下文 2.6?Add函数栈帧的销毁
?mov ? eax,dword ptr [ebp-8]? ,把ebp-8的值(30)放到eax里头去 ?pop edi? ,出栈,释放为edi创建的栈区,地址开始增大 ?pop esi? ,出栈,释放为esi创建的栈区,地址继续增大 ? ??pop ebx? ,出栈,释放为exb创建的栈区,地址继续增大 ?步骤演示图: add ? ? esp,0CCh? ?,为esp地址+0CCh,即退出Add程序的栈区空间? ,此时esp和ebp相同 ?步骤演示图:? ?mov? esp,ebp? ,ebp的值赋给esp,此时esp和ebp依旧相同 ?pop ? ?ebp? ,弹出ebp,并将ebp所指向的main函数的起始地址赋值给了ebp指针,esp指针向高位移动,esp和ebp重新开始维护main函数的栈区空间 ??ret? ,返回到main函数,在执行 ret 指令时,esp指针就指向了栈顶存放的call指令的下一条指令的地址, ??步骤演示图: ?此时Add函数的栈帧算是真正销毁 2.7?main函数代码继续执行
?add ? ?esp,8? ,而这一条指令的意思,是往esp里加8,即向高位移动,实际上这条指令就是在销毁我们的形参 ??步骤演示图: ??mov ? ? ?dword ptr [ebp-20h],eax? ,把eax的值放到ebp-20h上,而eax就是我们出Add函数时计算的和 ?接下来就是打印值和 main函数函数栈帧销毁,都与上面类似,这里不多做赘述 ???-------------------我是分割线------------------?? ?三、所需反汇编代码总览add 函数
main函数?
四、总结?1、局部变量是怎么创建的?
2、为什么未初始化的局部变量的值是随机值?
3、函数是如何传参的?以及传参的顺序是怎样的?
4、形参和实参是什么关系?
5、函数调用是怎么做的?
6、函数调用结束后是怎么返回的?
????-------------------我是分割线------------------?? 这里只是对函数栈帧的创建和销毁简单描述,需要更详细的百度即可 写完这篇文章给我的感觉就是图真难画.... ??-------------------我是分割线------------------?? 文章就先到这 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 12:46:12- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |