函数调用流程

article/2025/9/1 14:46:22

函数调用模型

  • 1. 函数调用流程
    • 函数调用流程分析
    • 函数参数调用代码分析
      • 自右向左入栈顺序的优点
  • 2. 调用惯例
    • 函数参数的传递顺序和方式
    • 栈的维护方式
    • 调用管理表
  • 3. 函数变量传递分析
    • 分析图

1. 函数调用流程

       栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今能见到的所有计算机的语言。在解释为什么栈如此重要之前,我们先了解一下传统的栈的定义:

       在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将压入栈中的数据弹出(出栈,pop),但是栈容器必须遵循一条规则:先入栈的数据最后出栈(First In Last Out,FILO).

       在经典的操作系统中,栈总是向下增长的。压栈的操作使得栈顶的地址减小,弹出操作使得栈顶地址增大。

栈在程序运行中具有极其重要的地位。最重要的,栈保存一个函数调用所需要维护的信息,这通常被称为堆栈帧(Stack Frame)或者活动记录(Activate Record)。

一个函数调用过程所需要的信息一般包括以下几个方面:

  • 函数的返回地址;
  • 函数的参数;
  • 临时变量;
  • 保存的上下文:包括在函数调用前后需要保持不变的寄存器。

函数调用流程分析

函数被调用的过程中,发生了如下图的栈操作:
在这里插入图片描述
从上图我们可以看到:C语言函数参数采用自右向左的入栈顺序(主要原因是为了支持可变长参数形式);当被调用函数返回时,以上压入栈中的所有空间都会被回收。

函数参数调用代码分析

大家可以运行一下下面的例子:

#include <stdio.h>void foo(int x, int y, int z)
{printf("x = %d at [%X]/n", x, &x);printf("y = %d at [%X]/n", y, &y);printf("z = %d at [%X]/n", z, &z);
}int main(int argc, char *argv[])
{foo(100, 200, 300);return 0;
}

他的运行结果是:

x = 100 at [BFE28760]
y = 200 at [BFE28764]
z = 300 at [BFE28768]

我们可以看到,Z的地址是最大的,上文我们也提到了:C程序栈底为高地址,栈顶为低地址,因此上面的实例可以说明函数参数入栈顺序的确是从右至左的。

自右向左入栈顺序的优点

C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数

通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。

2. 调用惯例

现在,我们大致了解了函数调用的过程,这期间有一个现象,那就是函数的调用者和被调用者对函数调用有着一致的理解,例如,它们双方都一致的认为函数的参数是按照某个固定的方式压入栈中。如果不这样的话,函数将无法正确运行。

如果函数调用方在传递参数的时候先压入a参数,再压入b参数,而被调用函数则认为先压入的是b,后压入的是a,那么被调用函数在使用a,b值时候,就会颠倒。

因此,函数的调用方和被调用方对于函数是如何调用的必须有一个明确的约定,只有双方都遵循同样的约定,函数才能够被正确的调用,这样的约定被称为”调用惯例(Calling Convention)”。

一个调用惯例一般包含以下几个方面:

函数参数的传递顺序和方式

函数的传递有很多种方式,最常见的是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。

对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序:从左向右,还是从右向左。有些调用惯例还允许使用寄存器传递参数,以提高性能。

栈的维护方式

在函数将参数压入栈中之后,函数体会被调用,此后需要将被压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由函数的调用方来完成,也可以由函数本身来完成。

为了在链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略。

事实上,在c语言里,存在着多个调用惯例,而默认的是cdecl.任何一个没有显示指定调用惯例的函数都是默认是cdecl惯例。比如我们上面对于func函数的声明,它的完整写法应该是:

int _cdecl func(int a,int b);

注意: _cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用__attribute__((cdecl))

调用管理表

调用惯例出栈方参数传递名字修饰
cdecl函数调用方从右至左参数入栈下划线+函数名
stdcall函数本身从右至左参数入栈下划线+函数名+@+参数字节数
fastcall函数本身前两个参数由寄存器传递,其余参数通过堆栈传递。@+函数名+@+参数的字节数
pascal函数本身从左至右参数入栈较为复杂,参见相关文档

3. 函数变量传递分析

可能大家会有疑问:主函数里还能嵌套函数呢?

在Linux下确实是可以的,但是在VS环境里就会报错。

大家可以看下面的例子:

他在gcc编译下运行下是这样的

#include <stdio.h>int main(int argc, char *argv[])
{int fun(void){printf("fun in main\n");}fun();return 0;
}

请添加图片描述

分析图

这里补一下栈区,堆区,全局区存储的数据类型知识:
栈区:由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。
堆区:由编程人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用malloc或者new进行堆的申请。
全局/静态区:全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间一直存在,它主要存储全局变量、静态变量和常量。

1.main函数在栈区开辟的内存,所有子函数均可以使用。
在这里插入图片描述
2.main函数在堆区开辟的内存,所有子函数均可以使用。
在这里插入图片描述
3.子函数1在栈区开辟的内存,子函数1和子函数2均可以使用,main函数不能使用。
在这里插入图片描述
4.子函数1在栈区开辟的内存,子函数1和2均可以使用,main函数也能使用。
在这里插入图片描述
5.子函数2在全局区开辟的内存,子函数1和main函数均可以使用
在这里插入图片描述
这里就不给大家一一举例子了,大家有兴趣可以试一下,留言大家一起探讨 Thanks♪(・ω・)ノ


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

相关文章

函数调用栈

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

函数调用和使用

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

C语言——函数的调用

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

C语言之函数调用

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

C语言函数的调用

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

Windows编程-001

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

windows全系1

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

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

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

Windows入门(一)

本人正在学习Windows编程操作&#xff0c;所以进行一些记录&#xff0c;希望对刚入门的个位有所帮助。 目录 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

学习目标&#xff1a; winfows10/11 安装wsl内核 基于wsl内核安装Ubuntu系统 基于ubuntu系统安装docker环境 学习内容&#xff1a; 系统下安装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利用率高&#xff0c;磁盘利用率高 解决方法&#xff1a; 方法一&#xff1a;禁用SuperFetch服务 计算机&#xff1a;—右键“管理”—SuperFetch—停止。或属性—-禁用。 开机就占用50%的内存&#xff08;共8G&#xff09; 关闭家庭组  家…

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

从1985年Windows 1.0正式诞生到2015年Windows 10诞生&#xff0c;微软花了三十年的时间&#xff0c;从像素化桌面到现在扁平化的界面。让我们来看一下Windows 1.0到Windows10三十年来的变化。 1、1985年11月20日&#xff0c;微软发布了第一版的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系统

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

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

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

Windows操作系统各版本的历史 Windows系统历史版本简介

30年间Windows系统有哪些版本&#xff1f;还记得你第一次了解到Windows操作系统存在的时候是哪一年吗&#xff1f;这些操作系统又有哪些特点呢&#xff1f;隐约知道计算机变得越来越小了吗……现在笔者将通过收集的资料&#xff0c;为各位细细解说曾经的操作系统。 30年间Wind…

[字符串]ASCII码表

数字&#xff0c;字母位置 数字0~9对应的ASCII码&#xff08;十进制&#xff09;袭为“48”~“57”大写字母A~Z对应的ASCII码&#xff08;十进制&#xff09;为“65”~“90”小写字母a~z对应的百ASCII码&#xff08;十进制&#xff09;为"97"~“122”表