我将添加另一个答案,以解决发生的一些切题讨论。
最初调用 C ABI(应用程序二进制接口)是为了以相反的顺序(即 - 从右到左推送)在堆栈上传递参数,调用者还释放堆栈存储空间。现代 ABI 实际上使用寄存器来传递参数,但许多错误的考虑可以追溯到最初的堆栈参数传递。
相比之下,原始的 Pascal ABI 将参数从左向右推送,而被调用者必须弹出参数。原始 C ABI 在两个重要方面优于原始 Pascal ABI。参数推送顺序意味着第一个参数的堆栈偏移量始终是已知的,允许具有未知数量参数的函数,其中早期参数控制有多少其他参数(ala printf
)。
C ABI 优越的第二种方式是调用者和被调用者不同意有多少参数时的行为。在 C 的情况下,只要您实际上不访问最后一个参数之后的参数,就不会发生任何不好的事情。在 Pascal 中,从堆栈中弹出错误数量的参数,整个堆栈被破坏。
最初的 Windows 3.1 ABI 基于 Pascal。因此,它使用了 Pascal ABI(从左到右顺序的参数,被调用者弹出)。由于参数编号的任何不匹配都可能导致堆栈损坏,因此形成了一种修改方案。每个函数名称都带有一个数字,表示其参数的大小(以字节为单位)。因此,在 16 位机器上,以下函数(C 语法):
int function(int a)
被修改为function@2
,因为int
是两个字节宽。这样做是为了如果声明和定义不匹配,链接器将无法找到函数,而不是在运行时破坏堆栈。相反,如果程序链接,那么您可以确保在调用结束时从堆栈中弹出正确数量的字节。
32 位 Windows 及更高版本使用stdcall
ABI。它类似于 Pascal ABI,除了推送顺序与 C 中的一样,从右到左。与 Pascal ABI 一样,名称 mangling 将参数字节大小更改为函数名称以避免堆栈损坏。
与此处其他地方的声明不同,C ABI 不会破坏函数名称,即使在 Visual Studio 上也是如此。相反,用 ABI 规范修饰的函数stdcall
并不是 VS 独有的。GCC 也支持此 ABI,即使在为 Linux 编译时也是如此。这被Wine广泛使用,它使用自己的加载器来允许将 Linux 编译的二进制文件运行时链接到 Windows 编译的 DLL。