14

像printf这样的可变参数函数如何找出它们得到的参数数量?

参数的数量显然不是作为(隐藏)参数传递的(请参阅此处 asm 示例中对 printf 的调用)。

有什么诀窍?

4

4 回答 4

14

诀窍是你以其他方式告诉他们。因为printf你必须提供一个格式字符串,它甚至包含类型信息(虽然这可能是不正确的)。提供此信息的方式主要是用户合同,而且通常容易出错。

至于调用约定:通常参数是从左到右压入堆栈,最后是回跳地址。调用例程清除堆栈。因此,被调用例程在技术上不需要知道参数的数量。

编辑:在 C++0x 中有一种安全的方式(甚至是类型安全的!)来调用可变参数函数!

于 2011-03-11T12:14:13.787 回答
9

隐含地,来自格式字符串。请注意, stdarg.h 不包含任何宏来检索传递的参数的总“可变”数量。这也是 C 调用约定要求调用者清理堆栈的原因之一,即使这会增加代码大小。

于 2011-03-11T12:32:50.423 回答
9

这就是为什么在 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");

这将转储堆栈的一部分。

于 2011-03-14T00:29:14.980 回答
5
  • 与任何标准 IA-32 调用约定不同,AMD64 System V ABI(Linux、Mac OS X)确实在al(RAX 的低字节)中传递了数字向量 (SSE / AVX) 可变参数。另请参阅:为什么在调用 printf 之前将 %eax 归零?

    但最多只能有 8 个(要使用的最大寄存器数)。而 IIRC,ABI 允许al大于XMM /YMM/ZMM 参数的实际数量,但不能少。所以它通常并不总是告诉你 FP args 的数量;8个以上不知道有多少,al可以多算。

    仅出于性能原因可使用,跳过将不需要的向量寄存器保存到“3.5.7 变量参数列表”中提到的“寄存器保存区域”。例如,GCC 编写代码进行测试al!=0,然后将 XMM0..7 转储到堆栈或什么都不做。(或者如果函数在任何地方使用VA_ARG__m256那么 YMM0..7。)

  • 在C层面上,除了其他人提到的解析格式字符串之外,还有其他技术。您还可以:

    • 像execl一样传递一个哨兵(void *)0来指示最后一个参数。

      您将希望使用sentinel函数属性来帮助 GCC 在编译时强制执行:C 警告 Missing sentinel in function call

    • 将其作为带有可变参数数量的额外整数参数传递

    • 使用formatfunction 属性来帮助 GCC 强制执行已知类型的格式字符串,例如printfstrftime

相关:如何在 gcc 中实现变量参数?

于 2015-07-20T15:11:40.093 回答