重庆免费网站建站模板,济南网络营销服务公司,网站建设的规划和设计,自己建设外贸网站由于我们要将c语言和汇编语言结合编程啦#xff0c;所以一定会存在汇编代码和c代码相互调用的问题#xff0c;有些事情还是要提前交待给大家的#xff0c;本节就是要给大家说下函数调用规约中的那些事儿。
函数调用约定是什么#xff1f;
调用约定#xff0c;calling co…由于我们要将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后也要调用函数也要传递参数呢还是会引出参数覆盖的问题。不过咱们之前说过参数可以放在寄存器中也可以放在内存中。
寄存器数量是有限的假设将参数放在寄存器中传递的话主调函数必然要考虑保存寄存器现场的问题一是用哪些寄存器传参数二是用于传递参数的寄存器其原来的值如果要保留的话往哪里保存呢估计大家也是这么想的内存足够大肯定是往内存中转储啦那既然是还要在内存中折腾不如直接把参数放在内存中更直接省事。
说到用内存来传递参数还要考虑内存地址用哪块内存来存储参数呢为了避免多进程的参数覆盖问题每个进程的参数得单独存储在不同地址得在内存中再为每个进程规划出一块存储参数的内存区域想想就很麻烦。或许您早已经迫不及待想说出答案啦栈也是位于内存中的啊最好的方式就是在栈中来保存。这有两个好处
首先每个进程都有自己的栈这就是每个内存自己的专用内存空间其次保存参数的内存地址不用再花精力维护已经有栈机制来维护地址变化了参数在栈中的位置可以通过栈顶的偏移量来得到。
好啦参数存储的问题解决了我们决定在进程自己的栈空间中保存参数 一种可行的方案是调用者在调用函数时先把所有参数压栈然后再调用函数。被调用函数在栈中获取到参数后进行处理。
以上方案如果不细想的话似乎还挺好其实解决了一个问题后又引入了两个新的问题
参数若在栈中保存由谁来负责回收参数所占的栈空间当参数很多的情况下主调函数将参数以什么样的顺序传递呢因为这决定被调用函数获取参数的准确性。
内心深处传来了小齐的《伤心太平洋》一波还未过去一波又来侵袭……
上面提到的回收栈空间或者清理栈空间并不是把参数在栈中所占据的内存清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, [ebp8] ;得到被减数参数a4 sub eax,[ebp12] ;得到减数参数b5 pop ebp ;恢复ebp的值
目前栈中的情况如图 如果调用者和被调用者subtract函数都是同一个程序员写的他很清楚自己压入栈中参数的顺序所以他在subtract函数中明确的知道栈中[ebp8]处的内容是被减数a[ebp12]处的是减数b。其实这个程序员在潜意识中自己跟自己建立了个约定先被压入栈的是减数b后被压入的是被减数a这样他才能确信从容地在subtract函数体中获取到正确的参数。其实他也可以反着来先把被减数a压入栈再把减数b压入栈这样在subtract函数中通过[ebp8]得到的是参数b减数[ebp12]得到的是参数a被减数。总之参数很多的情况下就会涉及到参数传递的顺序问题即使是自己负责传递参数的话也很少有人会今天一个“这样”的顺序传递参数明天一个“那样”的顺序传递参数这不得搞得人格分裂吗^_^因此参数传递的顺序应该是一始如终的要么从左到右要么从右到左只能选择一种。
以上是自己调用自己代码的情况怎么说都比较方便。可万一被调用函数subtract不是自己写的咱们不知道在subtract把[ebp8]是当做被减数a还是减数b咱们该以怎样的顺序将参数压入栈中呢这得跟人家商量了双方得协调个大家认同的参数入栈顺序这就是最初调用约定的由来。
我们要解决的不只是参数压栈顺序问题还有栈空间的清理工作呢。其实问题倒也不难解决这都是属于调用方和被调用方之间协调的问题只要双方提前商量好传入参数的顺序和由谁来负责清理栈空间就行。
一步步编写操作系统 64 常见的函数调用约定
在高级语言中这两个问题是通过调用约定来解决的调用约定就是调用方和被调用方就以上问题达成一致解决方案的约定双方按照这种约定合作就不会发生问题。我们按照由谁来清理栈空间分类目前的调用约定见表1