有关调用约定的历史 – 第一部分

有关调用约定的历史 – 第一部分

作者:BlogUpdater |  时间:2020-09-22 |  浏览:91 |  评论已关闭 条评论

蝎子
有关x86平台上的调用约定,有意思的一点是:选择实在是太多了。
在16位的世界,部分的调用约定由指令集来确定:BP寄存器默认为SS选择器,而其他的寄存器则默认是DS选择器。所以,BP寄存器主要用来访问基于栈的参数。另外,用于返回值的寄存器也会根据指令集来被自动选择。AX寄存器被用作累加器,它也很自然地被用来返回值。

8086指令集同时也有一些比较特殊的指令来将DX:AX当做一个32位的值,这样它就可以被用来返回一个32位的返回值。剩下的几个寄存器就是:SI, DI, BX和CX了。当决定哪个寄存器作为调用约定需要保留时,你需要权衡调用者和被调用者两者的需求。调用者可能会想着保留所有的寄存器,对于调用者来说,他就不需要担心调用前后的寄存器值的保存和恢复的问题。而被调用者可能希望不保留任何寄存器,这样他就不需要考虑在进入的时候保存和退出的时候恢复的问题。

如果你希望尽可能少的保留寄存器,则调用者将会需要编写很多用来保存恢复寄存器数据的代码。
如果你希望尽可能多的保留寄存器,那么就是被调用者有责任来保存恢复寄存器了,这个时候,调用者可不用关心这个事儿。

这个对于”叶子函数”(不调用任何其他函数)来说是非常重要的。而x86的指令集也会令事情变得更加复杂。CX寄存器可能不会用于内存访问,所以如果你希望使用其他的寄存器,则一个叶子函数至少可以在不保留任何寄存器的情况下进行内存访问。因此,BX可以被选为Scratch寄存器,而SI和DI将作为保留的寄存器。

下面是对16位版本的各种调用约定的一个小结:
All
在16位的世界,All调用约定会根据实际的大小,将保留BP, SI, DI这些寄存器, 并将返回值保存到DX:AX或者AX中,

C(__cdecl)
拥有可变参数的函数在大多数情况下会被强制使用C调用约定。
这就要求,调用者需要负责清理栈,同时,参数以从右至左的顺序入栈,所以第一个参数将位于相对于栈顶的一个固定位置。经典的C(pre-prototype)可以在不通知编译器实际请求的参数来进行函数调用,而一般的做法是向一个函数传输错误的参数个数,如果你知道被调用函数不介意的话。(例如,open这个API函数,如果第二个参数没有指定文件是否应该被创建,则第三个参数将会是可选的)

总结一下就是:调用者来清理栈,并且参数从右至左来入栈。
函数的装饰名词会包含下划线。我猜想这个下划线来避免它意外地和一些汇编语言关键字冲突。(例如,你调用了一个名叫call的函数)

Pascal(__pascal)
Pascal调用约定不支持可变参数,所以它主要用在那些”被调用者清栈”的场景。参数入栈的顺序是从左到右,为什么?你不觉得从左到右的顺序是一个很自然的顺序吗?函数的装饰名词会包含大写单词,因为Pascal不是一个大小写敏感的语言。
绝大多数的16位Windows API函数都使用Pascal调用约定进行导出。这种”被调用者清栈”的调用约定将在每个调用点省下3个字节,外加一个2个字节Overhead。所以,如果一个函数被调用了10次,你可以节省总共30个字节,然后在担负上2个字节的Overhead,总共可以净节省28字节的空间。这确实会有一点点的性能提升。要知道,在16位Windows时期,剩下几百个字节的空间和CPU时钟周期,可是意义重大。

Fortran(__fortran)
Fortran调用约定额Pascal相同,为什么相同的调用约定会起另外一个名字,是因为Fortran有一个奇怪的以引用传递的行为。

Fastcall(__fastcall)
Fastcall调用约定通过DX寄存器来传输第一个参数,然后通过CX寄存器传输第二个参数(至少我是这样认为的)。它的调用速度取决于具体的调用方式。通常它会快一点,因为通过寄存器传输参数的时候,不需要设计栈的访问,也不需要被调用者重新加载。
另一方面,如果在计算第一个和第二个参数的时候,涉及到一些很复杂的计算过程的话,调用者不得不借助于栈来实现。更加糟糕的是,被调用的函数还需要将寄存器的值载入到内存,因为它需要将寄存器空出来用于其他的目的,这在引入复杂计算过程的情况下,就会意味着两次内存访问。这可太不妙了。
作为结果,Fastcall调用约定仅仅对于简短的叶子函数会速度快一些。

总结
以上是我所能知道的16位世界的调用约定,如果有机会的话,我会在接下来的第二部分,叨叨下32位世界里的调用约定。

最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《The history of calling conventions, part 1》

评论已关闭。