vararg 函数如何找出机器代码中的参数数量?
How can variadic functions like printf find out the number of arguments they got?
The amount of arguments obviously isn't passed as a (hidden) parameter (see a call to printf in asm example here).
What's the trick?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
诀窍在于你以其他方式告诉他们。对于 printf,您必须提供一个格式字符串,其中甚至包含类型信息(尽管这可能不正确)。提供此信息的方式主要是用户契约,并且经常容易出错。
至于调用约定:通常将参数从左到右压入堆栈,最后返回跳转地址。调用例程清除堆栈。因此,被调用的例程没有技术需要知道参数的数量。
编辑:在 C++0x 中,有一种安全的方法(甚至类型安全!)来调用可变参数函数!
The trick is that you tell them somehow else. For
printf
you have to supply a format string which even contains type information (which might be incorrect though). The way to supply this information is mainly user-contract and often error-prone.As for calling conventions: Usually the arguments are pushed onto the stack from left to right and then the backjump address at last. The calling routine clears the stack. So there is no technical need for the called routine to know the number of parameters.
EDIT: In C++0x there is a safe way (even typesafe!) to call variadic functions!
隐式地,来自格式字符串。请注意,stdarg.h 不包含任何宏来检索传递的参数的“可变”总数。这也是 C 调用约定要求调用者清理堆栈的原因之一,尽管这会增加代码大小。
Implicitly, from the format string. Note that stdarg.h doesn't contain any macros to retrieve the total "variable" number of arguments passed. This is also one of the reasons the C calling convention requires the caller to clean the stack, even though this increases code size.
这就是为什么在 C 调用约定中参数按相反顺序推送的原因,例如:
如果调用:
堆栈最终如下所示:
使用距帧指针的偏移量间接访问参数(智能编译器可以省略帧指针)知道如何从堆栈指针计算事物)。在此方案中,第一个参数始终位于众所周知的地址,该函数访问与第一个参数告诉它的参数一样多的参数。
尝试以下操作:
这将转储部分堆栈。
This is the reason why arguments are pushed on reverse order on the C calling convention, e.g:
If you call:
The stack ends up like:
Arguments are accesed indirectly using its offset from the frame pointer (the frame pointer can be omitted by smart compilers that know how to calculate things from the stack pointer). The first argument is always at a well-known address in this scheme, the function accesses as many arguments as its first arguments tell it to.
Try the following:
This will dump part of the stack.
AMD64 System V ABI(Linux、Mac OS X )确实在 al (RAX 的低字节)中传递数字向量(SSE / AVX)可变参数,这与任何标准 IA-32 调用约定不同。另请参阅:为什么在调用 printf 之前将 %eax 归零?
但最多只能有 8 个(要使用的寄存器的最大数量)。 IIRC 中,ABI 允许
al
大于 XMM/YMM/ZMM 参数的实际数量,但不能小于。因此,它通常不会总是告诉您 FP 参数的数量;你无法分辨出超过 8 的个数,并且al
是允许超数的。仅出于性能原因,可以跳过将不需要的向量寄存器保存到“3.5.7 变量参数列表”中提到的“寄存器保存区域”。例如,GCC 编写测试
al!=0
的代码,然后将 XMM0..7 转储到堆栈或什么也不转储。 (或者,如果函数在任意位置使用VA_ARG
和__m256
,则 YMM0..7。)在 C 级别,除了解析格式字符串之外,还有其他技术正如其他人提到的。您还可以:
传递一个哨兵
(void *)0
来指示最后一个参数,例如execl 可以。您将需要使用
sentinel
函数属性来帮助 GCC 在编译时强制执行:C 警告函数调用中缺少哨兵将其作为额外的整数参数与可变参数的数量传递
使用
format
函数属性帮助 GCC 强制执行已知类型的格式字符串,例如printf
或strftime
< /里>
相关:gcc 中如何实现变量参数?
The AMD64 System V ABI (Linux, Mac OS X) does pass the number vector (SSE / AVX) varargs in
al
(the low byte of RAX), unlike any standard IA-32 calling conventions. See also: Why is %eax zeroed before a call to printf?But only up to 8 (the max number of registers to use). And IIRC, the ABI allows
al
to be greater than the actual number of XMM/YMM/ZMM args but it must not be less. So it does not in general always tell you the number of FP args; you can't tell how many more than 8, andal
is allowed to overcount.It's only usable for performance reasons, to skip saving unneeded vector registers to the "Register Save Area" mentioned in "3.5.7 Variable Argument Lists". For example GCC makes code that tests
al!=0
and then dumps XMM0..7 to the stack or nothing. (Or if the function usesVA_ARG
with__m256
anywhere, then YMM0..7.)On the C level, there are also other techniques besides parsing the format string as mentioned by others. You could also:
pass a sentinel
(void *)0
to indicate the last argument like execl does.You will want to use the
sentinel
function attribute to help GCC enforce that at compile time: C warning Missing sentinel in function callpass it as an extra integer argument with the number of varargs
use the
format
function attribute to help GCC enforce format strings of known types likeprintf
orstrftime
Related: How are variable arguments implemented in gcc?