一步步编写操作系统 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 相关…

【Gym - 101612C】【2017-2018NEERC】Consonant Fencity(状压枚举,预处理)

题干: 把26个字母分成19个辅音字母和7个元音字母,让你通过 将某些字母改为大写状态,使得字符串中连续的两个大小写状态不同的辅音字母组成的字母对数量最多,输出该状态下的字符串。注意输出的字符串中同一字母必须形态统一&#…

浅谈Mysql 表设计规范

本文首先探讨下数据库设计的三大范式,因为范式只是给出了数据库设计的原则,并没有告诉我们实际操作中应该怎样操作,应该注意什么,所以我们还会谈下实际工作中需要注意的具体操作问题。 三大范式 首先放出三大范式内容&#xff0c…

从零开始学视觉Transformer(1):Hello Vision Transformer

Vision Transformer打卡营分享一门很棒的 ViT 课程,课程详细介绍可以看这篇文章: 《Vision Transformer打卡营来啦!朱欤博士带你从零玩转ViT爆款模型!》

SQLServer中ISNULL、NULLIF和CONVERT函数

效率: UNION和UNION ALL关键字都是将两个结果集合并为一个,但这两者从使用和效率上来说都有所不同。 1、对重复结果的处理:UNION在进行表链接后会筛选掉重复的记录,Union All不会去除重复记录。 2、对排序的处理:Union…

一步步编写操作系统 66 浅析c库函数与系统调用1

本来说好的接下来的工作是要去“丰满”我们的内核,可咱们这种一步一回头的学习方式还得继续啊。其实我了解大家急切写内核的心情,但本书《操作系统真象还原》(请大家支持正版)的目的不是写一个操作系统就完事了,而是让…

给不会调用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…