调用一个函数时发生了什么?

news2024/10/3 10:32:21

欢迎来到 Claffic 的博客 💞💞💞 

前言:

用C语言写代码,如果一个工程相对复杂时,我们往往会采取封装函数的方式。在主函数中调用函数 这一看似简单的过程,实际上有很多不宜观察的细节,这篇博客我将带大家深入探究函数调用的每个细节。

注:

内容偏向底层原理,可能会比较复杂,但我相信看完后你会对函数调用有一个更加深刻的认识。


目录

💖Part1: 相关问题及概念铺垫

1.几个相关问题

2.寄存器

3.函数栈帧

4.函数调用栈

5.相关汇编指令

💗Part2: 函数栈帧的创建销毁具体过程

1.前期准备

2. main 函数预开辟栈帧

3.实参的创建和初始化

4.Add函数的调用

5.栈帧的销毁

❤️Part3: 问题答案揭晓


Part1: 相关问题及概念铺垫

1.几个相关问题

 • 局部变量是怎么创建的?

 • 为何局部变量出现屯屯烫烫等随机值?

 • 函数是怎么传参的?传参的顺序?

 • 实参和形参有何关系?

 • 函数调用的过程?

 • 函数调用结束,怎么返回?

如果没有进行函数栈帧的学习,我相信你也会像我当初一样懵逼🤣

好在接下来我会带大家逐步分析每一个过程,了解完整个过程后就会豁然开朗~

2.寄存器

寄存器是 CPU 内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。

常见的寄存器有:

eax: 累加(Accumulator)寄存器 , 常用于乘、除法和函数返回值

ebx: 基址(Base)寄存器 , 常做内存数据的指针, 或者说常以它为基址来访问内存

ecx: 计数器(Counter)寄存器 , 常做字符串和循环操作中的计数器

edx: 数据(Data)寄存器 , 常用于乘、除法和 I/O 指针

sbp: 基址指针(Base Point)寄存器 , 只做堆栈指针, 可以访问堆栈内任意地址, 经常用于中转           esp 中的数据

esp: 堆栈指针(Stack Point)寄存器 , 只做堆栈的栈顶指针; 不能用于算术运算与数据传送

有关函数栈帧的是 ebp , esp 这两个寄存器,其中存放的是地址,

这两个寄存器是用来 维护函数栈帧 的。

3.函数栈帧

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量

每一个函数调用,都要在 栈区 开辟一段空间。

例如,我写下这一段代码:

#include<stdio.h>
//这里把代码拆的很细,更加易于看清细节。
int Add(int x, int y)
{
	int 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 函数中调用了 Add 函数。

如图所示,在栈区为 main 函数开辟了一段空间,并且由 ebp 和 esp 这两个寄存器维护。

4.函数调用栈

函数调用栈是一种容器,具有后进先出的特性。在函数调用过程中,我们利用了栈的特性,当调用一个新的函数时,进行压栈Push,这个函数执行完进行出栈Pop。

简单来说,当有函数被调用时,该函数就被添加到栈中,在执行完所有任务后,该栈帧就会被删除。 

这时就要问了:main 函数也是函数,难道还有其他函数调用它吗?

是的,main 函数也是其他函数调用的,不过这在 Visual Studio 2013 中有体现。

下面我以 VS2013 演示:

调试 --> 窗口 --> 调用堆栈 

此时可以看到 main 函数被调用了:

 

按 F10 继续调试,直到程序结束:

此时看到了两个陌生的函数:

__tmainCRTStartup 和 mainCRTStartup

通过对 crtexe.c 文件的观察,我们可以得出下列结论:

对应栈帧的开辟:

5.相关汇编指令

我们是在反汇编的模式下观察函数栈帧的动作的,因此需要一些汇编指令: 

push:数据压入栈
pop:数据弹出栈

mov:数据转移

add:加法命令

sub:减法命令
call:函数调用
jump:转到目标函数,进行调用
ret:恢复返回地址

进行了相关知识的铺垫, 

那么接下来就是对具体动作的探究了:

Part2: 函数栈帧的创建销毁具体过程

1.前期准备

F10 调试 --> 鼠标右键 --> 转到汇编

在反汇编下可以清楚地观察函数栈帧的动作

2. main 函数预开辟栈帧

由于 main 函数是由其他函数调用的,所以在调用 main 函数之前就已经开辟好了相关函数的栈帧

00C21410  push  ebp       //将ebp压入
00C21411  mov   ebp,esp   //移动esp,让其指向压入的ebp;移动ebp,让其也指向压入的ebp
00C21413  sub   esp, 0E4h //esp减去0E4h,指向位置更低的空间,相当于为main函数预开辟空间

 执行完三步后的图示

//依次将ebx,esi,edi压入栈帧
00C21419  push  ebx
00C2141A  push  esi
00C2141B  push  edi

//从edi开始,将接下来39h个双字节都改为 OCCCCCCCCh(eax中的内容)
00C2141C  lea  edi, [ebp+FFFFFF1Ch]
00C21422  mov  ecx, 39h
00C21427  mov  eax, OCCCCCCCCh
00C2142C  rep  stos  dword ptr es:[edi]

在 main 函数预开辟之后,接下来就要执行有效的代码了:

3.实参的创建和初始化

我们继续:

int a = 10;
//将0A(十进制下是 10)放在 ebp-8 的位置上
00C2142E C7 45 F8 0A 00 00 00 mov dword ptr [ebp-8], 0Ah
int b = 20;
//将14(十进制下是 20)放在 ebp-14 的位置上
00C21435 C7 45 EC 14 00 00 00 mov dword ptr [ebp-14h], 14h
int c = 0;
//将0(十进制下是 0)放在 qbe-20 的位置上
00C2143C C7 45 E0 00 00 00 00 mov dword ptr [qbe 20], 0

 执行实参的创建和初始化

4.Add函数的调用

C = Add(a, b);
//创建形参并传值
00C21443 8B 45 EC         mov eax, dword ptr [ebp-14h]
00C21446 50               push eax
00C21447 8B 4D F8         mov ecx, dword ptr [ebp-8]
00C2144A 51 push          ecx
//调用函数,记录call下一次指令的地址,方便返回
00C2144B E8 91 FC FF FF   call 00C210E1
00C21450 83 C4 08         add esp,8
00C21453 89 45 E0         mov dword ptr [ebp- 20h], eax

此时才真正进入Add:

欸?是不是与之前 main 函数的调用有些相似?

对的,还是先压几个寄存器,再填充CCC...

接下来的就是把事先传过来的形参进行运算:

调用了数值之后将要返回的结果放入Add函数的栈帧中。

5.栈帧的销毁

//将 edi,esi,ebx 弹出
00C213F1 5F        pop   edi
00C213F2 5E        pop   esi
00C213F3 5B        pop   ebx
//移动 esp,ebp,找到高地址的寄存器
00C213F4 8B E5     mov   esp,ebp
00C213F6 5D        pop   ebp
//返回值
00C213F7 C3        ret

最终就把Add函数的栈帧销毁了。

Part3: 问题答案揭晓

回到开头的几个问题,在这里做一下回答:

 • 局部变量是怎么创建的?

先创建函数的栈帧,在函数栈帧里为局部变量分配空间。 

 • 为何局部变量出现屯屯烫烫等随机值?

在创建函数栈帧时会事先填充CCC...,打印出来就是 屯屯烫烫等随机值了,所以要养成局部变量初始化的习惯。

 • 函数是怎么传参的?

在调用函数之前就把参数压栈了,当函数中使用参数时,再通过指针偏移量找到事先压好的参数 

 • 实参和形参有何关系?

形参是实参的临时拷贝,两者的空间独立,形参的改变不会改变实参。 

 • 函数调用的过程?

压栈,创建空间...

 • 函数调用结束,怎么返回?

call 事先记录了下一条指令的地址,可以找到此位置,再通过寄存器带回。 


总结:

带大家探究了调用函数时的细节,重点是函数栈帧的创建和销毁。

码文不易 

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦  💗💗💗

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机组成原理|第三章(笔记)

目录第三章 系统总线3.1 总线的基本概念3.2 总线的分类3.2.1 片内总线3.2.2 系统总线3.2.3 通信总线3.3 总线特性及性能指标3.3.1 总线的特性3.3.2 总线性能指标3.4 总线结构3.4.1 单总线结构3.4.2 多总线结构3.4.3 总线结构举例3.5 总线控制3.5.1 总线判优控制3.5.2 总线通信控…

ALG和STUN

目录 ALG 应用层网关讲解 Client1使用FTP主动模式建立FTP Client1使用FTP被动模式建立FTP STUN讲解 ALG 应用层网关讲解 用来替换应用层信息 Client1使用FTP主动模式建立FTP 主动模式&#xff1a;服务器收到客户端发来的请求FTP的地址和端口 服务器使用20端口直接向客户端建…

webpack dll 提升构建速度

DLL&#xff0c;动态链接库&#xff08;Dynamic Link Library 或者 Dynamic-link Library&#xff09;&#xff0c;由微软公司提出。目的是为了节约应用程序所需的磁盘和内存空间。 在一个传统的非共享库中&#xff0c;如果两个程序调用同一个子程序&#xff0c;就会出现两份那…

Redis-6集群

文章目录前言Redis集群原理搭建Redis集群集群拓展后记前言 前两期介绍和搭建了Redis的主从复制架构和哨兵模式&#xff0c;虽然哨兵模式能够实现自动故障转移主备切换&#xff0c;一定程度上提高了系统的容错性 但这两种架构模式都不能解决单节点的并发压力和物理上线的问题&…

行测-判断推理-图形推理-样式规律-空间重构-立体拼合

有凸必有凹&#xff0c;排除A CD显然不对选BA和4能组成长方体B和3能组成长方体C和1能组成长方体选D这两个东西应该在同一侧&#xff0c;排除A C中间应该要由凸起&#xff0c;排除D选B图1向左旋转90图2逆时针旋转90选A

内网环境解决SSL证书问题

本来这个没什么好写的&#xff0c;但是坑实在有点多&#xff0c;不得不写个文章记录下来。 创建证书看这里&#xff01;&#xff01;&#xff01; 很多知识点要结合这个页面内容来看。 创建证书已经看过相关文章&#xff0c;然后用unity跑的时候发现连不上&#xff0c;完全没…

【Go】基于VS Code配置Go语言环境

教程 1. 安装go环境 打开官网&#xff1a;https://go.dev/dl/&#xff0c;找到对应系统的环境文件。 安装完成后软件会自动把路径添加到环境变量&#xff0c;可以在命令行中检验一下 2. 在VS Code中安装必要的插件 这一步我理解的就是提供语法高亮和代码提示 3. 打开一个路…

基于智能边缘和云计算的数字经济服务细粒度任务调度机制

数字经济被各国视为推动经济增长的必然选择&#xff0c;为经济高质量发展提供了新机遇、新路径。对于中国市场而言&#xff0c;云计算背后的强大基础是数字经济不可阻挡的发展趋势。在数字经济中&#xff0c;云作为基础设施成为构建数字经济金字塔的基础。为缓解数字经济服务器…

Windows下实现Linux内核的Python开发(WSL2+Conda+Pycharm)

许多软件可以通过Python交互&#xff0c;但没有开发Windows版本&#xff0c;这个时候装双系统或虚拟机都很不方便&#xff0c;可以采取WSL2CondaPycharm的策略来进行基于Linux内核的Python开发。启动WSL2&#xff0c;安装Linux内核教程&#xff1a;旧版 WSL 的手动安装步骤 | M…

交并比(IOU)的计算方法

交并比&#xff08;IOU, Intersetion Over Union&#xff09;,意思就是交集和并集的比值&#xff0c;用来评价两个几何图形的面积重合度&#xff0c;在目标检测算法模型中&#xff0c;通常被用来计算预测框与真实框的误差&#xff08;损失函数&#xff09;或者在非极大值抑制&a…

便捷式储能电源核心技术--单相逆变器设计

便捷式储能电源核心技术–单相逆变器设计 1.逆变器的规格参数 输入电压直流400V输出电压交流rms220V开关频率10kHz滤波电容6.23uF控制方式单极性倍频2.视频学习链接 视频学习链接 3.主电路仿真设计

日志服务搭建-ES-FileBeat-Kibana

1次订单量突增问题&#xff0c;导致了有几个数据没有录库&#xff0c;但是确有支付的记录&#xff0c;啥玩意&#xff0c;还能有这个操作&#xff0c;组内安排问题定位&#xff0c;解决&#xff0c;一看打出来的日志&#xff0c;只有支付有&#xff0c;生成订单这边没有&#x…

读书笔记-终身学习

前言人需要终身成长&#xff0c;也需要终身学习&#xff0c;以下是记录个人读书学习的笔记总结&#xff0c;希望能给大家一点借鉴&#xff0c;仅供参考。笔记1、《匠人精神》秋山利辉是日本神奈川县横滨市都筑区“秋山木工”的经营者&#xff0c;从事订制家具制作业务。是一家小…

Pinia不酸,保甜

为什么是Pinia 怎么说呢&#xff0c;其实在过往的大部分项目里面&#xff0c;我并没有引入过状态管理相关的库来维护状态。因为大部分的业务项目相对来说比较独立&#xff0c;哪怕自身功能复杂的时候&#xff0c;可能也仅仅是通过技术栈自身的提供的状态管理能力来处理业务场景…

使用Git Hook技术定义和校验代码提交模板

1.背景 使用Git做项目的版本控制时&#xff0c;在版本系统中会有很多的代码的提交记录&#xff0c;我们使用git log命令就会得到如下图中的提交记录&#xff1a; 当我们的项目比较简单&#xff0c;规模较小、开发人员也只有一两个的时候&#xff0c;其实可以不用定义代码的提…

HTML,JavaScript,JQuery合集

表单 <!DOCTYPE html> <html> <head><meta name"author" content"Yeeku.H.Lee(CrazyIt.org)" /><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title> 访问表单控件 <…

cli 开发工具 - NodeJS、Borwser 中制作漂亮的炫彩控制台

cli 界面制作、颜色计算工具 - JC Color 帮你在 NodeJS、Borwser 中制作漂亮的炫彩控制台 homepage&#xff1a;http://thispage.tech:9680/jclee1995/jc-color github&#xff1a;https://github.com/jacklee1995/jc-color npm&#xff1a;https://www.npmjs.com/package/jc-c…

2023年场外个股期权研究报告

第一章 概况 场外个股期权&#xff08;Over-the-Counter Equity Option&#xff09;&#xff0c;是指由交易双方根据自己的需求和意愿&#xff0c;通过协商确定行权价格、行权日期等条款的股票期权。与交易所交易的标准化期权不同&#xff0c;场外个股期权的合同内容可以根据交…

轻松学会css变量

css变量太无敌啦css变量是啥&#xff1f;css变量的语法css变量的使用场景主体切换响应式设计统一风格动态效果展示一下css变量是啥&#xff1f; CSS变量是一种用于存储和重复使用值的方法。它们可以在选择器中声明&#xff0c;并在整个样式表中使用。使用CSS变量可以提高代码的…

Kettle工具通过JNDI连接Oracle集群

我们在用Kettle ETL工具的时候&#xff0c;可能会遇到数据库为Oracle集群的模式&#xff0c;或者有时候目标库为oracle&#xff0c;在持续的循环调度中&#xff0c;经常发现oracle的数据库连接中断的情况&#xff0c;此时&#xff0c;在Kettle中有一个JNDI的连接方式能很好的解…