提问人:masterxilo 提问时间:3/11/2011 最后编辑:Peter Cordesmasterxilo 更新时间:8/30/2019 访问量:9147
vararg 函数如何找出机器代码中的参数数量?
How do vararg functions find out the number of arguments in machine code?
答:
诀窍是你以某种方式告诉他们。因为您必须提供一个格式字符串,其中甚至包含类型信息(尽管可能不正确)。提供这些信息的方式主要是用户合同,而且往往容易出错。printf
至于调用约定:通常参数从左到右推送到堆栈上,最后推送到后跳地址上。调用例程清除堆栈。因此,在技术上不需要被调用的例程来知道参数的数量。
编辑:在C++0x中,有一种安全的方法(甚至是类型安全!)来调用可变参数函数!
评论
隐式地,来自格式字符串。请注意,stdarg.h 不包含任何宏来检索传递的“变量”参数总数。这也是 C 调用约定要求调用方清理堆栈的原因之一,即使这会增加代码大小。
这就是为什么在 C 调用约定上以相反的顺序推送参数的原因,例如:
如果您致电:
printf("%s %s", foo, bar);
堆栈的最终结果是:
...
+-------------------+
| bar |
+-------------------+
| foo |
+-------------------+
| "%s %s" |
+-------------------+
| return address |
+-------------------+
| old frame pointer | <- frame pointer
+-------------------+
...
参数使用其与帧指针的偏移量间接访问(帧指针可以由知道如何从堆栈指针计算事物的智能编译器省略)。在此方案中,第一个参数始终位于已知地址,该函数访问的参数与其第一个参数告诉它的数量一样多。
请尝试以下操作:
printf("%x %x %x %x %x %x\n");
这将转储部分堆栈。
评论
%x
AMD64 System V ABI(Linux、Mac OS X)确实传递了数向量 (SSE / AVX) varargs(RAX 的低字节),这与任何标准的 IA-32 调用约定不同。Смотритетакже: 为什么在调用 printf 之前将 %eax 归零?
al
但最多只能有 8 个(要使用的最大寄存器数)。和 IIRC,ABI 允许大于 XMM/YMM/ZMM 参数的实际数量,但不得小于。因此,它通常不会总是告诉您 FP 参数的数量;你不能说出有多少超过8,并且被允许多算。
al
al
它只能出于性能原因,跳过将不需要的向量寄存器保存到“3.5.7 变量参数列表”中提到的“寄存器保存区域”。例如,GCC 生成测试然后转储 XMM0 的代码。7 堆栈或什么都没有。(或者,如果该函数与任何地方一起使用,则 YMM0..7.)
al!=0
VA_ARG
__m256
在 C 级别,除了解析其他人提到的格式字符串之外,还有其他技术。您还可以:
传递一个哨兵来指示最后一个参数,就像 execl 一样。
(void *)0
您将需要使用 function 属性来帮助 GCC 在编译时强制执行: C 警告 函数调用中缺少哨兵
sentinel
将其作为带有 varargs 个数的额外整数参数传递
使用 function 属性帮助 GCC 强制执行已知类型的格式字符串,例如
format
printf
strftime
评论
al
xmm
al
al
al
0
al=0
评论
printf