函数调用过程

article/2025/9/1 14:50:38

今天突然看到有人私信我说一直没写函数调用过程(栈帧的形成和销毁过程)这篇博文,赶紧补上。
刚看的栈帧内容时,我很迷惑,我觉得栈帧创建和销毁很麻烦,几句话根本说不完,而且我好像描述不清楚他的过程,所以在博文里面遇到函数调用我就规避了。现在再写栈帧调用过程,我觉得其实这个过程没有那么困难(不过还是有些抽象,毕竟计算机底层怎么运行我们也不是很明白)。
栈帧的创建的销毁过程例子代码:

int Add(int x,int y)
{int sum = 0;sum = x + y;return sum;
}int main ()
{int a = 10;int b = 12;int ret = 0;ret = Add(a,b);return 0;
}

今天主要用汇编代码去讲述这个过程,首先介绍几个寄存器和简单的汇编指令的意思。
先看几个函数调用过程涉及到的寄存器:
(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 调用函数。


http://chatgpt.dhexx.cn/article/dwHXCpS9.shtml

相关文章

浅谈函数调用!

导语 | 在任意一门编程语言中,函数调用基本上都是非常常见的操作;我们都知道,函数是由调用栈实现的,不同的函数调用会切换上下文;但是,你是否好奇,对于一个函数调用而言,其底层到底…

函数调用流程

函数调用模型 1. 函数调用流程函数调用流程分析函数参数调用代码分析自右向左入栈顺序的优点 2. 调用惯例函数参数的传递顺序和方式栈的维护方式调用管理表 3. 函数变量传递分析分析图 1. 函数调用流程 栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程…

函数调用栈

函数调用栈 我们在编程中写的函数,会被编译器编译为机器指令,写入可执行文件,程序执行的时候,会把这个可执行文件加载到内存,在虚拟地址空间中的代码段存放。 如果在一个函数中调用另一个函数,编译器就会…

函数调用和使用

1.函数是什么 函数(Function)能实现的功能从简单到复杂,各式各样,但其本质是相通的:“喂”给函数一些数据,它就能内部消化,给你“吐”出你想要的东西。 2.定义和调用函数 2.1 定义函数 #…

C语言——函数的调用

函数的调用 传值调用 函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。 传址调用 1.传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式 2.这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就…

C语言之函数调用

C语言之函数调用 “温故而知新,可以为师矣”! 让我们开启函数的道路吧! 今天主要讲函数的调用方式! 在讲之前,先回顾一下实际参数和形式参数的区别; 1.在定义函数时函数名后面括号中的变量名称为“形式参数…

C语言函数的调用

函数调用(Function Call),就是使用已经定义好的函数。函数调用的一般形式为: functionName(param1, param2, param3 ...);functionName 是函数名称,param1, param2, param3 …是实参列表。实参可以是常数、变量、表达…

Windows编程-001

如果建立的是Win32控制台工程(入口函数是main函数)的话,WinMain函数不能作为入口函数,如果想要解决这个问题的话,可以打开项目属性->链接器->系统->子系统,把子系统对应的“控制台”改为“窗口”。…

windows全系1

windows操作系统专贴(一定有你想要的) 2006年12月01日 22:25 windows 98 简体中文零售版第三版 语言:简体中文 类型:操作系统 大小:180MB 环境:9x/Me/NT/2000/XP/.Net/ 授权:零售版 软件介绍:这个版本是SE的改进版,比前…

对不起,说句粗话——这个太屌了,windows1.0安装程序(附下载)

今天逛一个软件论坛发现的,只有几百K。遥想当今我刚接触windows的版本是3.1,当时记得很清楚哦,进入windows要从dos命令行进入。现在一转眼,变成进入伪dos是运行栏里敲cmd了。 唉,想当年尿尿还能疵2米多远的时候&#…

Windows入门(一)

本人正在学习Windows编程操作,所以进行一些记录,希望对刚入门的个位有所帮助。 目录 1.什么是win32编程 2. 一个简单的win32程序 2.1 创建一个空项目 2.2 入口函数 2.3 注册窗口 2.3.1 窗口回调函数 2.4 创建窗口 2.5 显示窗口 2.6 更新窗口 3.…

win11网页版

网页版地址 点它 https://win11.blueedge.me/ 其github库地址其github库地址https://github.com/blueedgetechno/win11React/

windows10/11子系统安装ubuntu22.04

学习目标: winfows10/11 安装wsl内核 基于wsl内核安装Ubuntu系统 基于ubuntu系统安装docker环境 学习内容: 系统下安装wsl2下载ubuntu安装包windows11 安装ubuntu 22.04系统ubuntu 22.04 安装dockerdocker 启动、测试 windows下安装wsl2内核 1、如果未…

快速教你在虚拟机上完美安装Windows1.0

想必不用我说大家用虚拟机安装Windows1.0时都是这样的: 今天我就来教大家如何正确安装Windows1.0 因为在1985年(Windows1.0诞生的年代)的鼠标驱动已经不匹配我们的电脑了,所以我们需要先解决鼠标这个问题。 第一步: 打开UltraISO,在里面打开我们Windows1.0第一个镜像(…

Windows:

服务主机:本地系统(网络受限) CPU利用率高,磁盘利用率高 解决方法: 方法一:禁用SuperFetch服务 计算机:—右键“管理”—SuperFetch—停止。或属性—-禁用。 开机就占用50%的内存(共8G) 关闭家庭组  家…

Windows1.0到Windows10三十年进化史,你还记得自己最初使用的系统吗?

从1985年Windows 1.0正式诞生到2015年Windows 10诞生,微软花了三十年的时间,从像素化桌面到现在扁平化的界面。让我们来看一下Windows 1.0到Windows10三十年来的变化。 1、1985年11月20日,微软发布了第一版的Windows操作系统——Windows1.0。…

【Docker】 Windows10运行Windows镜像时常见错误

项目场景:【Docker】 Windows10运行Windows镜像常见错误 很多项目要求在Windows系统下运行,但开发环境的安装相对复杂,并且难以重新配置到新设备,甚至会出现多个项目的运行环境相互冲突和干扰的情况,这时候配置好一个镜像后就可以一劳永逸解决很多问题,直接用docker拉取Window…

Windows 1.0

Windows 1.0 是微软于1985年11月发布的第一款基于dos的pc图形操作平台.要安装Windows 1.0 先安装好ms-dos2.11.对于ms-dos3以上的,只能看见一个类似资源管理器的程序.因为现在很找到一个很破旧的电脑,所以只能通过虚拟机来实现.推荐使用VMware虚拟机.先进入dos2.11,如下图所示 …

重温经典:Windows1.0系统体验和尝试自己编写Windows1.0系统

相关说明 如果你觉得文章含有侵权部分,可以联系CSDN私聊,我会适当修改。 未经允许,不得转载,如需转载,请CSDN私聊。 Windows Windows,意思是窗口,窗户。当然,他也是一个操作系统的…

[笔记]深入解析Windows操作系统《一》概念和工具

文章目录 前言1.1 Windows操作系统的版本1.2 基础概念和术语Windows API关于 .NetWin32 API 历史服务、函数、例程进程、线程和作业进程tlist /t 查看进程树任务管理查看进程Process Explorer查看进程的细节 线程纤程与用户模式调度器线程 虚拟内存内核模式和用户模式终端服务及…