(动图详解)汇编视角观察函数栈帧的创建和销毁
目录
1、阅读本文的价值
2、函数栈帧及栈的概念
3、部分寄存器及汇编指令
4、main函数的调用
5、main函数的栈帧创建
6、变量的栈帧创建
6、函数传参
7、函数内部运算及销毁
8、通过函数栈帧引发的思考
1、局部变量是如何创建的?
2、 为什么局部变量不初始化是随机的?
3、函数调用时参数时如何传递的?传参的顺序是怎样的?
4、 函数的形参和实参分别是怎样实例化的?
5、 函数的返回值是如何带回的?
1、阅读本文的价值
话不多说先上图!
先从某招聘网站随便扒了两张岗位JD。由于博主在宁波,实体经济还是比较强的【外边真的全是嵌入式
那么面试官就会问了,小伙子你了解过汇编么?
我曾从汇编的角度描述过函数栈帧的创建与销毁!(本篇的意义在于了解汇编与深挖函数栈帧的创建与销毁,为后期理解栈区打好基础。)
2、函数栈帧及栈的概念
函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:
1、函数参数和函数返回值
2、临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
3、保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。
栈:用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入后出(First In Last Out, FILO)。在计算机系统中,栈则是一个具有以上属性的动态内存区域。压栈操作使得栈增大,而弹出操作使得栈减小。 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。
3、部分寄存器及汇编指令
1、部分寄存器
eax (通用寄存器) |
通常用来执行加法,函数调用的返回值一般也放在这里面 |
|
ebx (通用寄存器) |
保留临时数据 |
|
esp (通用寄存器) |
栈顶寄存器,指向栈的顶部 |
|
ebp (通用寄存器) |
栈底寄存器,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量 |
|
eip (指令寄存器) |
最重要的寄存器,它指向了下一条要执行的指令所存放的地址 |
2、部分汇编指令
mov (通用数据传送指令) |
数据转移指令 |
|
push (通用数据传送指令) |
数据入栈,同时esp栈顶寄存器也要发生改变 |
|
pop (通用数据传送指令) |
数据弹出至指定位置,同时esp栈顶寄存器也要发生改变 |
|
sub (算术运算指令) |
减法 |
|
add (算术运算指令) |
加法 |
|
call (子程序调用指令) |
函数调用1. 压入返回地址 2. 转入目标函数 |
|
jump(无条件程序转移指令) |
通过修改eip,转入目标函数,进行调用 |
|
ret (子程序或函数返回指令) |
恢复返回地址,压入eip,类似pop eip命令 |
4、main函数的调用
本次以加法函数为例:
#include <stdio.h>
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);printf("%d\n", c);return 0;
}
通过调用堆栈可以发现,main()函数被static int __cdecl invoke_main()进行调用,从__cdecl中能看出C语言函数参数的压栈的顺序是从右向左压入栈中,例如C函数 Fun(a,b,c)函数调用时,参数压栈顺序为 c , b , a。
main()函数结束时的return 0,返回至int const main_result中。
5、main函数的栈帧创建
1、main函数汇编代码
int main()
{
003A18B0 push ebp//在栈中压入ebp的值
003A18B1 mov ebp,esp//把esp的值给ebp
003A18B3 sub esp,0E4h//把esp的值减去0E4h
003A18B9 push ebx//在栈中压入ebx的值
003A18BA push esi//在栈中压入esi的值
003A18BB push edi//在栈中压入edi的值
003A18BC lea edi,[ebp-24h]//把ebp-24h放入edi中
003A18BF mov ecx,9//把9的值给ecx
003A18C4 mov eax,0CCCCCCCCh//把0CCCCCCCCh放入eax
003A18C9 rep stos dword ptr es:[edi]//从edi开始向下ecx的区域放入eax
2、动图详解
此处注意0CCCCCCCCh是从低地址向高地址创建的。
6、变量的栈帧创建
1、变量的汇编代码
int a = 10;
003A18D5 mov dword ptr [ebp-8],0Ah//在ebp-8位置处放入0Ah,即a的值 int b = 20;
003A18DC mov dword ptr [ebp-14h],14h//在ebp-14h位置处放入14,即b的值int c = 0;
003A18E3 mov dword ptr [ebp-20h],0//在ebp-20h位置处放入0,即c的值
2、动图详解
6、函数传参
1、传参的汇编代码
c = Add(a, b);
003A18EA mov eax,dword ptr [ebp-14h]//把ebp-14h地址的值放入eax中
003A18ED push eax//压入eax
003A18EE mov ecx,dword ptr [ebp-8]//把ebp-8地址的值放入ecx中
003A18F1 push ecx//压入ecx
003A18F2 call 003A10B4//调用add函数,栈顶保存call指令的下一条指令
003A18F7 add esp,8//形参销毁
003A18FA mov dword ptr [ebp-20h],eax//形参销毁
2、动图详解
注意传参时,形参是实参的一份临时拷贝,需要创建相同大小的空间用于传参,所以传值调用的效率往往不如传址调用。
7、函数内部运算及销毁
1、函数的汇编代码
int Add(int x, int y)
{
003A1770 push ebp
003A1771 mov ebp,esp
003A1773 sub esp,0CCh
003A1779 push ebx
003A177A push esi
003A177B push edi
003A177C lea edi,[ebp-0Ch]
003A177F mov ecx,3
003A1784 mov eax,0CCCCCCCCh
003A1789 rep stos dword ptr es:[edi]
003A178B mov ecx,3AC008h
003A1790 call 003A131B int z = 0;
003A1795 mov dword ptr [ebp-8],0 z = x + y;
003A179C mov eax,dword ptr [ebp+8]
003A179F add eax,dword ptr [ebp+0Ch]
003A17A2 mov dword ptr [ebp-8],eax return z;
003A17A5 mov eax,dword ptr [ebp-8]//把ebp-8的值放到eax寄存器中,让寄存器把结果带出函数
}
003A17A8 pop edi//弹出edi,同时esp地址增加
003A17A9 pop esi
003A17AA pop ebx
003A17AB add esp,0CCh
003A17B1 cmp ebp,esp
003A17B3 call 003A1244
003A17B8 mov esp,ebp
003A17BA pop ebp//通过ebp找回main的栈底
003A17BB ret
2、动图详解
通过eax寄存器把函数计算结果带给c。
8、通过函数栈帧引发的思考
1、局部变量是如何创建的?
首先需要为函数分配栈帧空间,在函数的栈帧空间初始化完成后,再为局部变量分配空间。
2、 为什么局部变量不初始化是随机的?
因为在栈帧空间初始化的过程中,通过动图演示,可以看到栈帧空间的部分区域被初始化为0CCCCCCCCh,若局部变量不初始化,将会被赋值为这个值,这也是“烫烫烫”的成因。(全局变量不初始化为0)
3、函数调用时参数时如何传递的?传参的顺序是怎样的?
函数调用时,会先在main函数内压入形参,函数参数通过从右向左的压栈方式向函数传递形参。当函数内部需要使用形参时,通过指针偏移量找到传参时生成的形参。
4、 函数的形参和实参分别是怎样实例化的?
在函数调用时,形参才开辟空间,形参与实参的值相同,但所属的空间不同,改变形参不会改变实参。
5、 函数的返回值是如何带回的?
在函数调用前,把call指令下一条指令的地址压入栈中,并且把上一个函数的ebp压入栈中,函数调用完毕,通过弹出ebp找到原始函数的栈底,同时使用压入栈中的地址找到下一条所要执行语句的地址。返回值是通过寄存器带出的。
关注!点赞!评论!收藏!关注!点赞!评论!收藏!关注!点赞!评论!收藏!关注!点赞!评论!收藏!关注!点赞!评论!收藏!
(动图详解)汇编视角观察函数栈帧的创建和销毁相关推荐
- 函数栈帧的创建与销毁
目录 前言 一.预备知识 1.内存区域的划分和分配 2.栈帧简介 3.寄存器简介 二.函数栈帧介绍 1.源代码 2.如何查看汇编代码 3.函数栈帧的创建与销毁(重点) 三.小彩蛋 总结 前言 最近在学 ...
- 函数调用过程详解:函数栈帧的创建与销毁
前言:我们在学习C语言的过程中,可以会产生很多疑问,比如: 局部变量是怎么创建的 为什么局部变量的值不做初始化就是随机值 函数是怎么传参的?传参的顺序是怎么样的? 形参和实参是什么关系? 函数调用是怎 ...
- 程序员内功心法之函数栈帧的创建和销毁
目录 1.本节目标 2.相关寄存器 3.相关汇编指令 4.什么是函数栈帧 5.什么是调用堆栈 6.函数栈帧的创建和销毁 (1).main函数栈帧的创建与初始化 (2).main函数的核心代码 (3). ...
- 【C语言】程序员筑基功法——《函数栈帧的创建与销毁》
<函数栈帧的创建与销毁> 文章目录 1. 前言 2. 问题引入 3. 前提准备 3.1 寄存器 3.2 汇编指令 4. 函数栈帧的维护 5. 如何调用堆栈 6. 函数栈帧的创建和销毁 6. ...
- 内功修炼《函数栈帧的创建和销毁》建议收藏
文章目录 前言 一. 寄存器的概念 二. 通用寄存器的结构 三. 指针寄存器和变址寄存器 四. EBP和ESP 五.总结 前言 在前期的学习过程中,我们可能会有很多的困惑: 1️⃣ 局部变量是怎么创建 ...
- 函数栈帧的创建和销毁图解
目录 一.问题: 二.寄存器 栈区 1.寄存器有哪些?有什么作用? 2.编译环境 3.栈区的使用习惯: 4.main函数也是被其他函数调用的 5.汇编代码 三.为main函数创建栈帧 1.main函数 ...
- 程序员内功修炼——函数栈帧的创建与销毁
一.什么是函数的栈帧 c语言是由函数构成的,那么函数是如何进行传参的?如何调用的?如何返回值的?这些问题与函数的栈帧有关. 函数栈帧:就是函数调用过程中程序的调用栈所开辟的空间,这些空间用来存放: 1 ...
- C语言内功修炼之函数栈帧的创建与销毁(举例加图解)
大家可能会函数栈帧不了解,可能都没有听过这个,不用着急,在理解函数栈帧之前,我们先来了解一下程序对内存使用的分区大概情况: 区域 作用 栈区(stack) 由编译器自动分配和释放,存放函数的参数值, ...
- 一文带你深入了解函数栈帧的创建和销毁
作者介绍:友友们好我是沐曦希,可以叫我小沐
最新文章
- 【scala】类的定义和单例对象
- is_best = recent_bleu4 > best_bleu4
- .netcore2.0 发布CentOS7
- dataframe修改列名_python dataframe操作大全数据预处理过程(dataframe、md5)
- java中截取部分字符串_JAVA中截取字符串substring用法详解
- 【guava】GuavaCache缓存失效的时候做一些操作 RemovalListener
- MSSQL 2012 密钥
- 银监计算机类考试题库,干货!国考银监会财经类面试题库
- [开源] PLC梯形图转指令表的算法源代码
- LSB算法分析与实现
- AB测试-最佳方案测试
- 13级计算机商务沟通与礼仪结课论文,商务沟通论文
- 常州一中训练试题泛做 Part 1
- count(1)、count(*)、count(列名) 详解
- 吴恩达:如何学习机器学习
- 微信小程序 展示地图指定位置导航
- java坦克大战案例_JAVA实现经典坦克大战源代码
- 008 解决问题的策略 转化(苏教版 五下)
- 内蒙古最新八大员安全员模拟真题题库及答案
- python 运行不过去SyntaxError: Non-ASCII character '\xc2' in file
热门文章
- 益圆木门 | 门墙同色,营造高级艺术范儿!
- 平均电流型LED降压恒流驱动器
- UE4 实现UMG 简单涂鸦
- Redis HA篇 +集群搭建
- c语言健康状况检查系统设计,智能健康监护仪设计(含电路原理图)
- [045] 微信公众平台开发教程第21篇-“可信网址”白名单
- 【QT 5 学习笔记-学习绘图相关+画图形图片等+绘图设备+基础学习(2)】
- 【Solidity】6. 合约 - 深入理解Solidity
- docker的基础使用
- dell系统重装后无法进入系统_小编告诉你dell装系统按哪个键进入bios