从汇编的角度解析函数调用过程

看看下面这个简单函数的调用过程:

 1 int Add(int x,int y)2 {3     int sum = 0;4     sum = x + y;5     return sum;6 }7 8 int main ()9 {
10     int a = 10;
11     int b = 12;
12     int ret = 0;
13     ret = Add(a,b);
14     return 0;
15 }

今天主要用汇编代码去讲述这个过程,首先介绍几个寄存器和简单的汇编指令的意思。 
先看几个函数调用过程涉及到的寄存器: 
(1)esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。 
(2)ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。 
(3)eax 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。 
(4)ebx 是”基地址”(base)寄存器, 在内存寻址时存放基地址。 
(5)ecx 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。 
(6)edx 则总是被用来放整数除法产生的余数。 
(7)esi/edi分别叫做”源/目标索引寄存器”(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串. 
在32位平台上,ESP每次减少4字节。 
再看几条简单的汇编指令: 
mov :数据传送指令,也是最基本的编程指令,用于将一个数据从源地址传送到目标地址(寄存器间的数据传送本质上也是一样的) 
sub:减法指令 
lea:取偏移地址 
push:实现压入操作的指令是PUSH指令 
pop:实现弹出操作的指令 
call:用于保存当前指令的下一条指令并跳转到目标函数。 
这些指令当然能看懂最好,可以让你很深刻的理解函数调用过程,不能看懂就只能通过我的描述去理解了。 
进行分析之前,先来了解下内存地址空间的分布:

栈空间是向低地址增长的,主要是用来保存函数栈帧。 栈空间的大小很有限,仅有区区几MB大小 
汇编代码实现: 
main函数汇编代码:

int main ()
{
011B26E0  push        ebp
011B26E1  mov         ebp,esp
011B26E3  sub         esp,0E4h
011B26E9  push        ebx
011B26EA  push        esi
011B26EB  push        edi
011B26EC  lea         edi,[ebp-0E4h]
011B26F2  mov         ecx,39h
011B26F7  mov         eax,0CCCCCCCCh
011B26FC  rep stos    dword ptr es:[edi] int a = 10;
011B26FE  mov         dword ptr [a],0Ah int b = 12;
011B2705  mov         dword ptr [b],0Ch int ret = 0;
011B270C  mov         dword ptr [ret],0 ret = Add(a,b);
011B2713  mov         eax,dword ptr [b]
011B2716  push        eax
011B2717  mov         ecx,dword ptr [a]
011B271A  push        ecx
011B271B  call        @ILT+640(_Add) (11B1285h)
011B2720  add         esp,8
011B2723  mov         dword ptr [ret],eax return 0;
011B2726  xor         eax,eax
}
011B2728  pop         edi
011B2729  pop         esi
011B272A  pop         ebx
011B272B  add         esp,0E4h
011B2731  cmp         ebp,esp
011B2733  call        @ILT+450(__RTC_CheckEsp) (11B11C7h)
011B2738  mov         esp,ebp
011B273A  pop         ebp
011B273B  ret            

Add函数汇编代码:

int Add(int x,int y)
{
011B26A0  push        ebp
011B26A1  mov         ebp,esp
011B26A3  sub         esp,0CCh
011B26A9  push        ebx
011B26AA  push        esi
011B26AB  push        edi
011B26AC  lea         edi,[ebp-0CCh]
011B26B2  mov         ecx,33h
011B26B7  mov         eax,0CCCCCCCCh
011B26BC  rep stos    dword ptr es:[edi] int sum = 0;
011B26BE  mov         dword ptr [sum],0 sum = x + y;
011B26C5  mov         eax,dword ptr [x]
011B26C8  add         eax,dword ptr [y]
011B26CB  mov         dword ptr [sum],eax return sum;
011B26CE  mov         eax,dword ptr [sum]
}
011B26D1  pop         edi
011B26D2  pop         esi
011B26D3  pop         ebx
011B26D4  mov         esp,ebp
011B26D6  pop         ebp
011B26D7  ret              

下面图中详细描述了调用过程地址变化(此处所有地址是取自32位windows系统vs编辑器下的调试过程。):

过程描述: 
1、参数拷贝(参数实例化)。 
2、保存当前指令的下一条指令,并跳转到被调函数。 
这些操作均在main函数中进行。

接下来是调用Add函数并执行的一些操作,包括: 
1、移动ebp、esp形成新的栈帧结构。 
2、压栈(push)形成临时变量并执行相关操作。 
3、return一个值。 
这些操作在Add函数中进行。

被调函数完成相关操作后需返回到原函数中执行下一条指令,操作如下: 
1、出栈(pop)。 
2、回复main函数的栈帧结构。(pop ) 
3、返回main函数 
这些操作也在Add函数中进行。 至此,在main函数中调用Add函数的整个过程已经完成。 
总结起来整个过程就三步: 
1)根据调用的函数名找到函数入口; 
2)在栈中审请调用函数中的参数及函数体内定义的变量的内存空间 
3)函数执行完后,释放函数在栈中的审请的参数和变量的空间,最后返回值(如果有的话) 
如果你学了微机原理,你会想到cpu中断处理过程,是的,函数调用过程和中断处理过程一模一样。

函数调用约定: 
这里再补充一下各种调用规定的基本内容。 
_stdcall调用约定

所有参数按照从右到左压入堆栈,由被调用的子程序清理堆栈

_cdecl调用约定(The C default calling convention,C调用规定)

参数也是从右到左压入堆栈,但由调用者清理堆栈。

_fastcall调用约定

顾名思义,_fastcall的目的主要是为了更快的调用函数。它主要依靠寄存器传递参数,剩下的参数依然按照从右到左的顺序压入堆栈,并由被调用的子程序清理堆栈。

本篇博文是按调用约定__stdcall 调用函数。

csdn博客地址:http://blog.csdn.net/qq_38646470

C语言的函数调用过程(栈帧的创建与销毁)相关推荐

  1. 函数调用过程详解:函数栈帧的创建与销毁

    前言:我们在学习C语言的过程中,可以会产生很多疑问,比如: 局部变量是怎么创建的 为什么局部变量的值不做初始化就是随机值 函数是怎么传参的?传参的顺序是怎么样的? 形参和实参是什么关系? 函数调用是怎 ...

  2. 【C语言】程序员筑基功法——《函数栈帧的创建与销毁》

    <函数栈帧的创建与销毁> 文章目录 1. 前言 2. 问题引入 3. 前提准备 3.1 寄存器 3.2 汇编指令 4. 函数栈帧的维护 5. 如何调用堆栈 6. 函数栈帧的创建和销毁 6. ...

  3. (动图详解)汇编视角观察函数栈帧的创建和销毁

    目录 ​1.阅读本文的价值 ​2.函数栈帧及栈的概念 ​3.部分寄存器及汇编指令 ​4.main函数的调用 5.main函数的栈帧创建 ​6.变量的栈帧创建 ​6.函数传参 ​7.函数内部运算及销毁 ...

  4. 函数栈帧的创建与销毁

    目录 前言 一.预备知识 1.内存区域的划分和分配 2.栈帧简介 3.寄存器简介 二.函数栈帧介绍 1.源代码 2.如何查看汇编代码 3.函数栈帧的创建与销毁(重点) 三.小彩蛋 总结 前言 最近在学 ...

  5. 程序员内功心法之函数栈帧的创建和销毁

    目录 1.本节目标 2.相关寄存器 3.相关汇编指令 4.什么是函数栈帧 5.什么是调用堆栈 6.函数栈帧的创建和销毁 (1).main函数栈帧的创建与初始化 (2).main函数的核心代码 (3). ...

  6. 内功修炼《函数栈帧的创建和销毁》建议收藏

    文章目录 前言 一. 寄存器的概念 二. 通用寄存器的结构 三. 指针寄存器和变址寄存器 四. EBP和ESP 五.总结 前言 在前期的学习过程中,我们可能会有很多的困惑: 1️⃣ 局部变量是怎么创建 ...

  7. C语言内功修炼之函数栈帧的创建与销毁(举例加图解)

    大家可能会函数栈帧不了解,可能都没有听过这个,不用着急,在理解函数栈帧之前,我们先来了解一下程序对内存使用的分区大概情况:  区域 作用 栈区(stack) 由编译器自动分配和释放,存放函数的参数值, ...

  8. 程序员内功修炼——函数栈帧的创建与销毁

    一.什么是函数的栈帧 c语言是由函数构成的,那么函数是如何进行传参的?如何调用的?如何返回值的?这些问题与函数的栈帧有关. 函数栈帧:就是函数调用过程中程序的调用栈所开辟的空间,这些空间用来存放: 1 ...

  9. 函数栈帧的创建和销毁图解

    目录 一.问题: 二.寄存器 栈区 1.寄存器有哪些?有什么作用? 2.编译环境 3.栈区的使用习惯: 4.main函数也是被其他函数调用的 5.汇编代码 三.为main函数创建栈帧 1.main函数 ...

最新文章

  1. 2022-2028年中国塑料鞋行业市场发展调研及未来前景规划报告
  2. ThinkPHP实现定时执行任务的两种方法 - 博客频道 - CSDN.NET
  3. 032_jdbc-mysql批量操作
  4. APM - 使用JavaAgent+Javassit 插桩C3P0
  5. deprecated_@Deprecated新外观可能是什么?
  6. cocoapods-安装
  7. Scrapy网络爬虫框架实战[以腾讯新闻网为例]
  8. LeetCode 60. 第k个排列(python、c++)
  9. java 集合练习题2
  10. 计算机绘图中级,计算机绘图(中级)
  11. SDelete-Gui – 用右键安全的删除文件,不可恢复[Windows]
  12. 重装战姬电脑版模拟器怎么玩
  13. python读取nc文件并转换成csv_在Python3中读取crystal report.rpt文件并将其转换为.csv或.xlsx...
  14. adguard home上网慢_AdGuard Home:用 DNS 巧去广告,所有设备都能用
  15. Flurry、友盟、TalkingData,Google analytic移动应用统计分析对比
  16. 多重网格法解泊松方程(两步法)
  17. Semantic Proximity Search on Heterogeneous Graph by Proximity Embedding
  18. 公司企业邮箱怎么注册开通?
  19. RN + Flutter
  20. openmv底层算法剖析---梦飞openmv前传

热门文章

  1. 需求又变了,要不要怼回去?
  2. 「mysql优化专题」这大概是一篇最好的mysql优化入门文章(1)
  3. 从入门到熟悉 HTTPS 的 9 个问题
  4. CSS之定位布局(position,relative定位布局技巧)
  5. Java数字反转(编程题)
  6. android四大组件 简书,android四大组件
  7. 数据中心的“芯”竞争
  8. pdf常用字体包 -baijiahao_PDF 的各种操作,我用 Python 来实现(附网站和操作指导)
  9. Java高版本编译低版本运行_Java高版本编译低版本运行错误(ConcurrentHashMap.keySet)...
  10. ad软件 pcb如何走线过孔_【经验】关于高速PCB设计的一些经典问答