assembly 为什么stdcall不能处理不同数量的参数?

c86crjj0  于 2022-11-13  发布在  其他
关注(0)|答案(4)|浏览(131)

我的理解是,对于cdecl调用约定,调用方负责清理堆栈,因此可以传递任意数量的参数。
另一方面,stdcall被调用方会清理堆栈,因此无法接收不同数量的参数。
我的问题有两个:

  1. stdcall函数不能也得到一个关于有多少变量的参数,并做同样的事情吗?
  2. cdecl函数如何知道它们收到了多少个参数?
axr492tv

axr492tv1#

stdcall函数不能也得到一个有多少变量的参数,并做同样的事情吗?
是的,当然。你可以发明 * 任何 * 调用约定。但那就不再是stdcall了。
cdecl函数如何知道它们收到了多少个参数?
它们不会。它们假定在调用约定指定的位置找到所需数量的参数。如果缺少这些参数,则这是代码无法观察到的bug。编译以下代码:

printf("%s");

即使它缺少一个参数。结果是未定义的。对于printf风格的函数,编译器通常会发出警告(如果可以的话),这是由于了解函数的内部结构,但这不是一个可以普遍应用的解决方案。
如果调用方提供了错误的参数数量或类型,则该行为未定义。

fjnneemd

fjnneemd2#

stdcall函数不能也得到一个有多少变量的参数,并做同样的事情吗?
如果调用者必须传递一个单独的arg,其中包含要弹出的字节数,那么在调用之后,要做的工作就比add esp, 16或其他操作要多(cdecl风格的caller-pops)。这将完全违背stdcall的目的,stdcall的目的是在每个调用位置保存几个字节的空间,特别是对于不会延迟跨几个调用弹出参数的简单代码生成,或者使用mov存储重新使用由推送分配的空间。(每个函数通常有多个调用站点,因此ret imm16ret相比多出的2个字节将分摊到这些调用站点上。)

更糟糕的是,被调用方无法在x86 / x86-64上有效地使用变量数ret imm16只能处理立即数(常量嵌入在机器代码中),因此要在返回地址上方弹出一个可变字节数,函数将不得不复制堆栈中较高的返回地址,并从那里执行普通的ret。(或者通过将返回地址弹出到寄存器中,使分支返回地址分支预测失效.)

另请参阅:

cdecl函数如何知道它们收到了多少个参数?
"他们不会“
C语言的设计假设变量函数不知道它们接收了多少个参数,因此函数需要一个格式字符串或sentinel之类的东西来知道要迭代多少个参数。例如,POSIX execl(3)execve(2)系统调用的 Package 器)接受一个以NULL结尾的char*参数列表。

因此,调用约定通常不会在提供作为侧通道的计数上浪费代码大小和周期;函数需要的任何信息都将是真实的的C级参数的一部分。

有趣的事实:printf("%d", 1, 2, 3)在C语言中是well-defined behaviour,并且需要它来安全地忽略格式字符串引用的参数之外的参数。
因此,使用stdcall并基于format-string进行计算是行不通的。你是对的,如果你想创建一个适用于变量函数的被调用方弹出约定,你需要在某个地方传递一个大小,比如在寄存器中。但是就像我前面所说的,调用方知道正确的数字,所以让调用方管理堆栈会容易得多。而不是让被调用者以后再去挖掘这个额外的参数。这就是为什么现实世界中没有调用约定是这样工作的。

vhipe2zx

vhipe2zx3#

在被调用方中传递参数的数目可以清除堆栈约定,但额外参数的额外开销超过了它的有用性。它会浪费额外参数的堆栈空间,并使被调用方的堆栈处理复杂化。
发明stdcall的原因是因为它使代码更小。(在x86或其他体系结构上,当寄存器中传递的参数过多时)。x86甚至有一个retn #指令,其中#是要调整的字节数。Windows NT在其开发早期从cdecl切换到stdcall,据说它减少了大小并提高了速度(我相信Larry Osterman在博客上提到了这一点(迷你答案here))。
cdecl函数不知道有多少个参数。您可以(在ABI级别上)传递比函数实际使用的参数更多的参数。printf样式函数将使用format参数作为“向导”来逐个访问参数。完成此操作后,还必须通知被调用方每个参数的类型(因此它知道大小,这进而以实现定义的方式允许它遍历参数列表。在Windowsx86上,参数在堆栈上,在遍历堆栈时,您只需要参数size来计算它们的偏移量)。va_list及其在stdarg.h中的宏为C函数访问这些参数提供了帮助。

pzfprimi

pzfprimi4#

我的总结,基于@IInspectable的回答。
stdcall函数也可以得到一个变量个数的参数,但这样就不再是stdcall了。
cdecl不知道要读取多少个参数,我们假设函数能够根据预先确定的参数数量导出参数的数量,就像printf的格式字符串一样。
如果调用者提供的参数少于可以派生的参数,或者是一个意外的类型,那么该行为是未定义的。

相关问题