一步步编写操作系统 62 函数调用约定

由于我们要将c语言和汇编语言结合编程啦,所以一定会存在汇编代码和c代码相互调用的问题,有些事情还是要提前交待给大家的,本节就是要给大家说下函数调用规约中的那些事儿。

函数调用约定是什么?

调用约定,calling conventions,从字面上理解,它是调用函数时的一套约定,是被调用代码的接口,它体现在:

  • 参数的传递方式,是放在寄存器中?栈中?还是两者混合;
  • 参数的传递顺序,是从左到右传递?还是从右到左;
  • 是调用者保存寄存器环境还是被调用者保存?保存哪些寄存器呢?

我估计,我这么解释调用约定的话,之前对此不懂的同学还是不懂,所以咱们得从头说起啦。没例子还真说不清楚,咱们还是拿例子来说事吧。

比如在c语言中我们有这样的代码

int subtract(int a, int b) {return a-b;
}

我们可以用这样的形式调用它:

int sub = subtract(3,2);

这样sub的值就变成了1。这是我们司空见惯的用法,但大家有没有想过,计算机是如何确定参数3和2在哪里的呢?这是有关参数存储的问题。

计算机中可没有专门存储参数的硬件,即使有的话,我想也不太容易确定该硬件的容量,毕竟参数的个数是不定的。而且还有个致命的问题,若在刚刚传入参数之后,函数执行之前被换下了cpu,新的进程上cpu后,也要调用函数,也要传递参数呢,还是会引出参数覆盖的问题。不过咱们之前说过,参数可以放在寄存器中,也可以放在内存中。

寄存器数量是有限的,假设将参数放在寄存器中传递的话,主调函数必然要考虑保存寄存器现场的问题,一是用哪些寄存器传参数,二是用于传递参数的寄存器,其原来的值如果要保留的话,往哪里保存呢?估计大家也是这么想的,内存足够大,肯定是往内存中转储啦,那既然是还要在内存中折腾,不如直接把参数放在内存中更直接省事。

说到用内存来传递参数,还要考虑内存地址,用哪块内存来存储参数呢?为了避免多进程的参数覆盖问题,每个进程的参数得单独存储在不同地址,得在内存中再为每个进程规划出一块存储参数的内存区域,想想就很麻烦。或许您早已经迫不及待想说出答案啦:栈也是位于内存中的啊,最好的方式就是在栈中来保存。这有两个好处:

  1. 首先,每个进程都有自己的栈,这就是每个内存自己的专用内存空间;
  2. 其次,保存参数的内存地址不用再花精力维护,已经有栈机制来维护地址变化了,参数在栈中的位置可以通过栈顶的偏移量来得到。

好啦,参数存储的问题解决了,我们决定在进程自己的栈空间中保存参数, 一种可行的方案是,调用者在调用函数时,先把所有参数压栈,然后再调用函数。被调用函数在栈中获取到参数后进行处理。

以上方案如果不细想的话似乎还挺好,其实解决了一个问题后,又引入了两个新的问题:

  1. 参数若在栈中保存,由谁来负责回收参数所占的栈空间?
  2. 当参数很多的情况下,主调函数将参数以什么样的顺序传递呢?因为这决定被调用函数获取参数的准确性。

内心深处传来了小齐的《伤心太平洋》:一波还未过去,一波又来侵袭……

上面提到的回收栈空间或者清理栈空间,并不是把参数在栈中所占据的内存清0,而是回收参数所在的内存空间,也就是指让栈顶恢复到栈中参数所在的位置之前,即让栈指针往高地址处回退。这样一来,参数原本占用的空间又变得可用了,下次再有入栈操作时,push指令可以直接将其覆盖。

也许有部分同学并未意识到这两个问题,心想,我自己写的函数,我自己调用,难道我自己还不知道怎么处理吗?您看,这里用了三个“我自己”来强调问题的关键所在,自己调用自己的代码确实可以避免以上两个问题,只要自己协调好了就一切ok,可保不准您会调用其他同事写的函数。

调用约定是为解决汇编语言的问题才提出的,不像咱们平时所用的高级语言,直接用实参往函数中一代入就算调用完成了,高级语言中本身不存在这两个问题,高级语言编译器为了方便程序员,默默承担了这些,这两个问题是高级语言在被编译为底层汇编语言时才有的,所以高级语言中不涉及调用约定。

在c语言中,咱们不用考虑这些问题,还是拿前面说过的减法函数举例:

subtract(int a, int b) {  //被调用者return a-b;
}
int sub = subtract(3,2);  //主调用者

函数subtract是返回a减b的差,这里只要代入实参3和2即可完成调用。可是,在其被编译为汇编语言时,参数是要压入栈中的,现在问题来了……我们模拟一下这种情况,以上c代码中的调用方和被调用方对应的汇编代码如下:

主调用者:

 1 push 2    ;压入参数b2 push 3    ;压入参数a3 call subtract   ;调用函数subtract

被调用者:

 1 push ebp   ;备份ebp,为以后用ebp做为基址来寻址参数2 mov ebp, esp  ;将当前栈顶赋值给ebp3 mov eax, [ebp+8] ;得到被减数,参数a4 sub eax,[ebp+12] ;得到减数,参数b5 pop ebp    ;恢复ebp的值

目前栈中的情况如图

 

如果调用者和被调用者(subtract函数)都是同一个程序员写的,他很清楚自己压入栈中参数的顺序,所以他在subtract函数中,明确的知道栈中[ebp+8]处的内容是被减数a,[ebp+12]处的是减数b。其实,这个程序员在潜意识中自己跟自己建立了个约定,先被压入栈的是减数b,后被压入的是被减数a,这样他才能确信从容地在subtract函数体中获取到正确的参数。其实他也可以反着来,先把被减数a压入栈,再把减数b压入栈,这样在subtract函数中通过[ebp+8]得到的是参数b(减数),[ebp+12]得到的是参数a(被减数)。总之参数很多的情况下就会涉及到参数传递的顺序问题,即使是自己负责传递参数的话,也很少有人会今天一个“这样”的顺序传递参数,明天一个“那样”的顺序传递参数,这不得搞得人格分裂吗^_^,因此参数传递的顺序应该是一始如终的,要么从左到右,要么从右到左,只能选择一种。

以上是自己调用自己代码的情况,怎么说都比较方便。可万一,被调用函数subtract不是自己写的,咱们不知道在subtract把[ebp+8]是当做被减数a还是减数b,咱们该以怎样的顺序将参数压入栈中呢?这得跟人家商量了,双方得协调个大家认同的参数入栈顺序,这就是最初调用约定的由来。

我们要解决的不只是参数压栈顺序问题,还有栈空间的清理工作呢。其实问题倒也不难解决,这都是属于调用方和被调用方之间协调的问题,只要双方提前商量好传入参数的顺序和由谁来负责清理栈空间就行。

一步步编写操作系统 64 常见的函数调用约定

在高级语言中,这两个问题是通过调用约定来解决的,调用约定就是调用方和被调用方就以上问题达成一致解决方案的约定,双方按照这种约定合作就不会发生问题。我们按照由谁来清理栈空间分类,目前的调用约定见表1

 

 

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

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

相关文章

重读经典:《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》

ViT论文逐段精读【论文精读】这次李沐博士邀请了亚马逊计算机视觉专家朱毅博士来精读 Vision Transformer(ViT),强烈推荐大家去看本次的论文精读视频。朱毅博士讲解的很详细,几乎是逐词逐句地讲解,在讲解时把 ViT 相关…

给不会调用C++STL库中二分函数lower_bound,upper_bound,binary_search同学的一些话!

lower_bound算法返回第一个大于等于给定值所在的位置。设置两个指针start和last,其中start指向数组的起始位置,last指向数组末尾位置之后的位置。当start和last指向相同位置时循环结束。mid指向[start,last)区间的中间位置,当中间位置元素值大…

详解IMU标定经典论文:A Robust and Easy to Implement Method for IMU Calibration without External Equipments

本文介绍一篇 关于IMU 标定的经典论文,论文收录于 ICRA14,在论文中作者介绍了如何不适用外部设备标定 IMU 加速度和角速度偏差、尺度系数、轴偏移参数。 论文链接:https://readpaper.com/paper/2021503353、https://readpaper.com/paper/221…

重读经典:《Momentum Contrast for Unsupervised Visual Representation Learning》

MoCo 论文逐段精读【论文精读】这次论文精读李沐博士继续邀请了亚马逊计算机视觉专家朱毅博士来精读 Momentum Contrast(MoCo),强烈推荐大家去看本次的论文精读视频。朱毅博士和上次一样讲解地非常详细,几乎是逐词逐句地讲解,在讲…

【HRBUST - 1623】Relation(思维模拟,拆解字符串)

题干: 一天,ikki在看书的时候发现书上有个类似于家谱状的插图,突然ikki想到了一个有趣的现象:有时候用某个人一连串 的关系描述另一个人的时候,最后可能还是他本身。例如:小明的爸爸的爸爸和小明的爷爷是同…

一步步编写操作系统 67 系统调用的实现1-2 68

接上文: 系统调用的子功能要用eax寄存器来指定,所以咱们要看看有哪些系统调用啦,在linux系统中,系统调用是定义在/usr/include/asm/unistd.h文件中,该文件只是个统一的入口,指向了32位和64位两种版本。在a…

【HDU - 6662】Acesrc and Travel(树形dp,博弈dp)

题干: Acesrc is a famous tourist at Nanjing University second to none. During this summer holiday, he, along with Zhang and Liu, is going to travel to Hong Kong. There are nnspots in Hong Kong, and n−1n−1 bidirectional sightseeing bus routes …

一步步编写操作系统 69 汇编语言和c语言共同协作 70

由于有了上一节的铺垫,本节的内容相对较少,这里给大家准备了两个小文件来实例演示汇编语言和c语言相互调用。 会两种不同语言的人,只是掌握了同一件事物的两种表达方式。人在学习一种新语言时,潜意识里是建立了语言符号与事物形象…

一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74

一直以来,我们在往屏幕上输出文本时,要么利用bios中断,要么利用系统调用,这些都是依赖别人的方法。咱们还用过一个稍微有点独立的方法,就是直接写显存,但这貌似又没什么含量。如今我们要写一个打印函数了&a…

【CodeForces - 208C 】Police Station(单源最短路条数,起点终点建图,枚举顶点)

题干: The Berland road network consists of n cities and of m bidirectional roads. The cities are numbered from 1 to n, where the main capital city has number n, and the culture capital — number 1. The road network is set up so that it is possi…

【Chrome浏览器】常用快捷键整理

标签页和窗口快捷键 1. Ctrl n 打开新窗口 2. Ctrl t 打开新的标签页,并跳转到该标签页 3. Ctrl Shift t 重新打开最后关闭的标签页,并跳转到该标签页 4. Ctrl Tab 跳转到下一个打开的标签页 5. Ctrl Shift Tab 跳转到上一个打开的标签页 6.…

【人工智能课程实验】 - 利用贝叶斯分类器实现手写数字 的识别

读入数据与预处理 因为老师给的文件无法直接读取,故从官网导入数据: 官网链接:http://www.cs.nyu.edu/~roweis/data.html 导入数据之后要对MATLAB文件进行读入: datasio.loadmat(trainfile) 对文件type一下: ty…

一步步编写操作系统 77 内联汇编与ATT语法简介

内联汇编 之前和大家介绍过了一种汇编方法,就是C代码和汇编代码分别编译,最后通过链接的方式结合在一起形成可执行文件。 另一种方式就是在C代码中直接嵌入汇编语言,强大的GCC无所不能,咱们本节要学习的就是这一种,它…

【Python学习】内置函数(不断更新)

关于常用在for循环中的range函数 python range() 函数可创建一个整数列表,一般用在 for 循环中。 函数语法 range(start, stop[, step]) 参数说明: start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range&#…

【Python学习】 简单语法与常见错误(持续更新)

关于单引号和双引号 当输出的字符串内部没有单引号的时候,外面可以用单引号, 但是如果内部有了单引号,那么外部只能用双引号。 dict {Name: Zara, Age: 7, Class: First} print(dict) print (dict[Name]: , dict[Name]) print ("dic…

一步步编写操作系统 78 intel汇编与ATT汇编语法区别

本节咱们介绍下intel汇编语法和at&t汇编语法的区别。 以上表中未列出这两种语法在内存寻址方面的差异,个人觉得区别还是很大的,下面单独说说。 在Intel语法中,立即数就是普通的数字,如果让立即数成为内存地址,需要…

重读经典:《Masked Autoencoders Are Scalable Vision Learners》

MAE 论文逐段精读【论文精读】这一次李沐博士给大家精读的论文是 MAE,这是一篇比较新的文章,2021年11月11日才上传到 arXiv。这篇文章在知乎上的讨论贴已经超过了一百万个 view,但是在英文社区,大家反应比较平淡一点,R…

【Python学习日志】 - Numpy包

NumPy是什么? 使用Python进行科学计算的基础包,在数据分析的时候比较常用到矩阵计算。这时太多的Np属性不记得,所以方便自己使用把一些常用的Np属性汇总记录一下使用的时候方便查找。 ndarray.ndim 阵列的轴数(尺寸)…

详解协同感知数据集OPV2V: An Open Benchmark Dataset and Fusion Pipeline for Perception with V2V Communication

在《详解自动驾驶仿真框架OpenCDA: An Open Cooperative Driving Automation Framework Integrated with Co-Simulation》 一文中介绍了自动驾驶仿真框架 OpenCDA。本文将介绍论文作者另一篇最新工作 OPV2V,论文收录于 ICRA2022。 OPV2V 数据集主要 feature 有&…