RISC-V基础之函数调用(五)函数递归调用及函数参数数量溢出(超出现有寄存器个数)约定(包含实例)
首先先解释一下栈在函数调用中的作用,更详细的部分请参照考研复习之数据结构笔记(五)栈和队列(上)(包含栈的相关内容)_管二狗赶快去工作!的博客-CSDN博客
函数嵌套调用栈的作用是用来保存和恢复函数调用过程中的相关信息,如参数、局部变量、返回地址、上下文等。这些信息可以帮助函数在执行完毕后返回到正确的位置,以及在发生异常时恢复到合理的状态。函数嵌套调用栈的具体结构和操作取决于编译器、操作系统和体系结构的设计,但一般来说,它遵循以下原则:
- 当一个函数被调用时,它会在栈顶分配一段空间,称为栈帧(stack frame)。栈帧中存放了该函数的参数、局部变量、返回地址、上下文等信息。
- 当一个函数调用另一个函数时,它会将新的栈帧压入栈顶,形成一个嵌套的结构。这样,每个函数都可以访问自己的栈帧中的信息,而不会影响其他函数的信息。
- 当一个函数执行完毕后,它会将自己的栈帧弹出栈顶,释放空间。然后,它会根据返回地址跳转回调用者,并将返回值传递给调用者。
- 当一个函数发生异常时,它会将异常信息保存在栈中,并跳转到异常处理程序。异常处理程序可以根据栈中的信息进行恢复或终止操作。
为了更好地理解函数嵌套调用栈的作用,我们可以看一个简单的例子。假设我们有以下三个函数:
int f1(int a, int b) {int c = a + b;int d = f2(c);return d;
}int f2(int x) {int y = x * x;int z = f3(y);return z;
}int f3(int r) {int s = r - 1;return s;
}
现在我们假设主程序调用了 f1(2, 3)。那么函数嵌套调用栈的变化如下:
- 主程序在调用
f1(2, 3)之前,会将参数a = 2和b = 3压入栈中,并将返回地址(主程序中f1(2, 3)的下一条指令)压入栈中。然后跳转到f1的起始地址。 f1在执行时,会在栈顶分配一段空间,存放自己的局部变量c和d。然后计算c = a + b = 5并保存在栈中。f1在调用f2(c)之前,会将参数x = c = 5压入栈中,并将返回地址(f1中f2(c)的下一条指令)压入栈中。然后跳转到f2的起始地址。f2在执行时,会在栈顶分配一段空间,存放自己的局部变量y和z。然后计算y = x * x = 25并保存在栈中。f2在调用f3(y)之前,会将参数r = y = 25压入栈中,并将返回地址(f2中f3(y)的下一条指令)压入栈中。然后跳转到f3的起始地址。f3在执行时,会在栈顶分配一段空间,存放自己的局部变量s。然后计算s = r - 1 = 24并保存在栈中。f3在执行完毕后,会将自己的返回值s = 24放在a0寄存器中,并将自己的栈帧弹出栈顶,释放空间。然后,它会根据返回地址跳转回f2。f2在接收到f3的返回值后,会将其保存在栈中的z中。然后,它会将自己的返回值z = 24放在a0寄存器中,并将自己的栈帧弹出栈顶,释放空间。然后,它会根据返回地址跳转回f1。f1在接收到f2的返回值后,会将其保存在栈中的d中。然后,它会将自己的返回值d = 24放在a0寄存器中,并将自己的栈帧弹出栈顶,释放空间。然后,它会根据返回地址跳转回主程序。- 主程序在接收到
f1的返回值后,会将其保存在某个变量中。然后,它会将栈中的参数a = 2和b = 3弹出栈顶,释放空间。然后,它会继续执行下一条指令。
递归函数是一种非叶函数,它调用自身。递归函数既是调用者又是被调用者,所以它必须保存和恢复保留和非保留寄存器。
例如,阶乘函数可以写成一个递归函数。回忆一下,阶乘(n) = n × (n – 1) × (n – 2) × ⋯ × 2 × 1。阶乘函数可以递归地写成阶乘(n) = n × 阶乘(n – 1),如下图所示。1 的阶乘就是 1。

假设程序从地址 0x8500 开始。根据被调用者保存规则,阶乘是一个非叶函数,必须保存 ra。根据调用者保存规则,阶乘在调用自身后仍然需要 n,所以它必须保存 a0。因此,它在开始时将这两个寄存器压入栈中。然后它检查 n 是否小于等于 1。如果是的话,它将返回值 1 放在 a0 中,恢复栈指针,并返回到调用者。在这种情况下,它不需要恢复 ra,因为它从未被修改过。如果 n 大于 1,函数递归地调用阶乘(n−1)。然后它从栈中恢复 n 和返回地址寄存器 (ra),进行乘法,并返回这个结果。注意,函数巧妙地将 n 恢复到 t1 中,以免覆盖返回值。乘法指令 (mul a0,t1,a0) 将 n (t1) 和返回值 (a0) 相乘,并将结果放在 a0 中,即返回寄存器。
为了清楚起见,我们在函数调用开始时保存寄存器。一个优化的编译器可能会观察到当 n 小于等于 1 时,没有必要保存 a0 和 ra,并且只在函数的 else 部分将寄存器保存到栈上。下图显示了执行阶乘(3) 时的栈情况。
为了说明,我们假设 sp 最初指向 0xFF0(高地址位为 0),如上图(a) 所示。函数创建了一个两字的栈帧来保存 n (a0) 和 ra。在第一次调用时,阶乘将 a0(保存 n = 3)保存在 0xFEC 和 ra 在 0xFE8 中,如图 (b) 所示。函数然后将 n 改变为 2 并递归地调用阶乘(2),使 ra 持有 0x8528。在第二次调用时,它将 a0(保存 n = 2)保存在 0xFE4 和 ra 在 0xFE0 中。这次我们知道 ra 包含 0x8528。函数然后将 n 改变为 1 并递归地调用阶乘(1)。
在第三次调用时,它将 a0(保存 n = 1)保存在 0xFDC 和 ra 在 0xFD8 中。这次 ra 再次包含 0x8528。第三次调用阶乘返回值为 1 的 a0,并在返回到第二次调用之前释放栈帧。第二次调用恢复 n(到 t1)为 2,恢复 ra 到 0x8528(它恰好已经有了这个值),释放栈帧,并返回 a0 = 2 × 1 = 2 给第一次调用。第一次调用恢复 n(到 t1)为 3,恢复 ra,调用者的返回地址,释放栈帧,并返回 a0 = 3 × 2 = 6。图© 显示了递归调用的函数返回时的栈情况。当阶乘返回到调用者时,栈指针在它的原始位置(0xFF0),栈指针以上的栈内容没有改变,并且所有的保留寄存器保持它们的原始值。a0 持有返回值,6。
另外,在函数调用过程中如果一个RISC-V函数需要的参数超过了八个寄存器,即a0到a7,那么它应该按照标准调用约定的规则,将多余的参数通过堆栈(stack)传递。具体来说,调用者(caller)在进行函数调用前,需要将多余的参数按照顺序压入堆栈中,并且在调用后将它们从堆栈中弹出。被调用者(callee)在接收到参数后,需要从堆栈中按照相反的顺序取出多余的参数,并且在返回前将它们放回堆栈中。这样可以保证函数调用前后堆栈的内容和指针不变,以及参数的正确传递。
例如,如果一个函数需要10个整数参数,那么它可以将前八个参数放在寄存器a0到a7中,将后两个参数压入堆栈中。被调用者可以从寄存器a0到a7中直接读取前八个参数,从堆栈中读取后两个参数。在返回前,被调用者需要将后两个参数放回堆栈中。调用者在函数返回后,需要将后两个参数从堆栈中弹出。
图(b) 显示了被调用者堆栈帧的组织。 堆栈帧保存临时、参数和返回地址寄存器(如果由于后续函数调用而需要保存它们),以及函数将修改的任何已保存寄存器。 它还保存局部数组和任何多余的局部变量。 如果被调用者有超过八个参数,它会在调用者的堆栈帧中找到它们。
相关文章:
RISC-V基础之函数调用(五)函数递归调用及函数参数数量溢出(超出现有寄存器个数)约定(包含实例)
首先先解释一下栈在函数调用中的作用,更详细的部分请参照考研复习之数据结构笔记(五)栈和队列(上)(包含栈的相关内容)_管二狗赶快去工作!的博客-CSDN博客 函数嵌套调用栈的作用是用…...
力扣:48. 旋转图像(Python3)
题目: 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 来源:力扣(LeetCode) 链接&…...
HarmonyOS应用开发者基础与高级认证题库——中级篇
系列文章目录 HarmonyOS应用开发者基础与高级认证题库——基础篇 HarmonyOS应用开发者基础与高级认证题库——中级篇 文章目录 系列文章目录前言一、判断二、单选三、多选 前言 今天刚换了台果子手机就收到了华子鸿蒙开发认证邀请(认证链接)࿰…...
Python中实现多个列表、字典、元组、集合的连接
目录 目录 前言 一、列表 1、运算符 2、extend()方法 3、解包操作 * 二、字典 1、update()方法 2、解包操作 ** 三、元组 1、 运算符 2、解包操作 * 四、集合 1、union方法 2、| 运算符 3、解包操作 * 五、不同类…...
1005 继续(3n+1)猜想
描述 卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对 n3 进行验证的时候,我们需要计算 3、5、8、4、…...
基于图片、无人机、摄像头拍摄进行智能检测功能
根据要求进行无人机拍摄的视频或图片进行智能识别,开发过程需要事项 1、根据图片案例进行标记,进行模型训练 2、视频模型训练 开发语言为python 根据需求功能进行测试结果如下 根据车辆识别标记进行的测试结果截图 测经过查看视频 8月1日...
Boost开发指南-4.2ignore_unused
ignore_unused 编写代码的过程中有时会出现一些暂时用不到但又必须保留的变量,GCC等编译器会对此发出警告,使用-Wunused可以关闭这些警告消息,不过这也有可能导致潜在的隐患。古老的办法是使用(void)var的形式来“使用”一下变量,…...
【Mybatis】XML映射文件
目录 11.3XML映射文件 1.select 2.insert、update、delete 3.Sql 4.parameters(参数) 5.resultMap 6.resultMap 使用示例 (1)在先前创建的数据库stu中创建表student 2,并插入若干条数据,代码如下: (2)创建工程mybatis_ResultMap_demo。 (…...
11.2【MyBatis】主配置文件
目录 11.2【MyBatis】主配置文件 1.properties(属性) 2.settings(设置) 3.typeAliases(别名) 4.typeHandlers类型处理器 5.objectFactory 对象工厂 6.plugins(插件) 7.environments (配置环境) 8.mappers (映射器) 11.2【MyBatis】主配置文件 MyBatis的 …...
linuxARM裸机学习笔记(2)----汇编LED灯实验
MX6ULL 的 IO IO的复用功能 这里的只使用了低五位,用来配置io口,其中bit0~bit3(MUX_MODE)就是设置 GPIO1_IO00 的复用功能的,GPIO1_IO00 一共可以复用为 9种功能 IO,分别对应 ALT0~ALT8。每种对应了不同的功能 io的属性配置 HY…...
用C语言实现插入排序算法
1.设计思路 用插入排序对长度为n的待排序数组A进行排序的伪代码(在代码中,A中元素的数目n用A.length来表示)。 伪代码如下: INSERTION-SORT(A) for j2 to A.length:keyA[j] //将A[j]插入已排序序列A[1..j-1]ij-1while i>0…...
2023 电赛E题--可能会出现的问题以及解决方法
2023年电赛E题报告模板(K210版)--可直接使用 本文链接:2023年电赛E题报告模板(K210版)--可直接使用_皓悦编程记的博客-CSDN博客 解决激光笔在黑色区域无法识别 本文链接: 2023 电赛 E 题 激光笔识别有误-…...
Demystifying Prompts in Language Models via Perplexity Estimation
Demystifying Prompts in Language Models via Perplexity Estimation 原文链接 Gonen H, Iyer S, Blevins T, et al. Demystifying prompts in language models via perplexity estimation[J]. arXiv preprint arXiv:2212.04037, 2022. 简单来说就是作者通过在不同LLM和不同…...
WEB集群——http、tomcat
1. 简述静态网页和动态网页的区别。 2. 简述 Webl.0 和 Web2.0 的区别。 3. 安装tomcat8,配置服务启动脚本,部署jpress应用。 1. 简述静态网页和动态网页的区别。 1)、静态网页 (1)、什么是静态网页 请求响应信息&…...
Socks5代理:网络安全与爬虫之利器
一、Socks5代理:简介与工作原理 Socks5代理,全称为Socket Secure 5代理,是一种允许用户通过代理服务器进行网络连接的技术。它是Socks协议的最新版本,在网络安全和数据传输方面有着显著的优势。 Socks5代理与其他代理的不同之处在…...
如何兼容低版本浏览器
如何兼容低版本浏览器 分为三个部分来说 HTML 低版本浏览器无法识别新增的HTML5元素,如果要兼容这部分浏览器,需要做以下处理: 对于非可替换元素,比如article、section、header、footer等,这种元素虽然低版本浏览器不识…...
【雕爷学编程】MicroPython动手做(39)——机器视觉之图像基础2
MixPY——让爱(AI)触手可及 MixPY布局 主控芯片:K210(64位双核带硬件FPU和卷积加速器的 RISC-V CPU) 显示屏:LCD_2.8寸 320*240分辨率,支持电阻触摸 摄像头:OV2640,200W像素 扬声器&#…...
gitlab搭建
回到目录 GitLab 是一个用于仓库管理系统的开源项目,使用 Git 作为代码管理工具,并在此基础上搭建起来的 Web 服务。 Gitlab 是被广泛使用的基于 git 的开源代码管理平台, 基于 Ruby on Rails 构建, 主要针对软件开发过程中产生的代码和文档进行管理,…...
JMeter 的使用
文章目录 1. JMeter下载2. JMeter的使用2.1 JMeter中文设置2.2 JMeter的使用2.2.1 创建线程组2.2.2 HTTP请求2.2.3 监听器 1. JMeter下载 官网地址 https://jmeter.apache.org/download_jmeter.cgi https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.2.zip 下载解…...
Java语言 Iterator 如何装换成 List
迭代器如何逆向转换成List集合 在 Java 中,迭代器(Iterator)是一种用于遍历集合中元素的对象,它提供了一种简单而一致的方式来访问集合中的元素,而不需要暴露集合内部的结构。如果我们需要将一个迭代器逆向转换成 Lis…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
