如何使用"..."(变量)参数?

How to use "..." (variable) argument?

本文关键字:参数 何使用 变量      更新时间:2023-10-16

可能的重复项:
根据 C 和 C++,什么是可变参数函数?

我已经printf()函数中看到...论点。printfscanf等功能究竟是如何工作的?它们怎么会有无限的输入值?

这依赖于 C 调用约定,即调用方从堆栈中弹出参数。

因为调用方知道有多少个参数(以及它们的大小(,所以它必须以某种方式将其传达给被调用方。对于 printf(( 和 family,这是通过格式字符串实现的。对于 execv 和 family,这是通过具有终止 NULL 参数来指示的。

被调用方使用标准标头 stdarg.h 中定义的宏从堆栈中读取参数。

从内存(粗略(中,您需要定义一个类型 va_list 的变量,该变量使用 va_start 从前面的参数初始化,如下所示:

void print(const char* format, ...)
{
  va_list args;
  va_start(args, format);

  /* read an int */
  int i = va_arg(args, int);
  /* read an char* */
  char* pc = va_arg(args, char*);

  va_end(args);
}

显然,您必须解析格式字符串才能知道是读取 int、char*、双精度还是 ...

为此,您必须了解堆栈上传递的底层函数调用和参数。

调用函数时,需要在堆栈上写入一些内容,例如返回地址、指向上一个堆栈帧的指针等。写在堆栈上的另一部分由函数的参数组成。在 C 中,参数从右到左推送到堆栈上(与例如 Pascal 不同(。这样,函数的第一个参数位于堆栈上参数列表的顶部。(这不是标准强制要求的,而是现实中发生的事情(。

现在,printfscanf的工作方式非常简单。他们得到一个字符串作为第一个参数(它位于堆栈的顶部(我的意思是堆栈上的参数列表,但我只是简单地写在堆栈顶部((。在该字符串的指导下,他们尝试从格式字符串所在的下方(在堆栈上(检索其他值。因此,例如,如果您调用:

printf("%d %c %sn", 0x12345678, 'a', "str");

printf在堆栈上看到的是(堆栈顶部在左侧,假设 int = 4 字节,小端和 64 位地址(:

<address of format string (8 bytes)>|0x78|0x56|0x34|0x12|0x61|0x00|0x00|0x00|<address of str (8 bytes)>

因此,它所做的是读取格式字符串,到达%d,然后从格式字符串地址下方读取 4 个字节(因此它读取 |0x78|0x56|0x34|0x12| 部分(然后看到%c并从之后读取四个字符(|0x61|0x00|0x00|0x00(并将其解释为字符等。 (%c读取四个字符而不是一个字符的原因, 是否通过...发送的char(以及short(会自动转换为int

但是,您应该注意,printfscanf盲目地这样做。因此,如果您发送一个double作为 printf 的参数并读取 %d ,它会读取相同数量的字节 (4(,但将这些位解释为int而不是double --> 你得到的是胡言乱语。此外,如果你有很多%(如%d%c等(但没有足够的参数,printfscanf盲目地检查其他堆栈变量并将它们解释为数据/指针。

最后,现在优秀的编译器会为您读取格式字符串,并在格式字符串中的预期参数数量与发送的实际参数(或其类型(不匹配时向您发出警告。 gccclang甚至允许您将函数声明为遵循相同的printfscanf格式,因此您也可以在自定义函数上获得这种额外的保护。

以下是一些有用的链接:

http://en.wikipedia.org/wiki/Variadic_function

http://www.gnu.org/s/hello/manual/libc/Variadic-Functions.html

http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=138

希望这些对您有所帮助。

另外,请花一些时间在网上搜索这些问题。

手动到va_*

从技术上讲,被调用的函数必须具有有关在每个特定情况下调用它的参数的确切信息(在 *printf(( 的情况下,信息以格式字符串的形式传递(。有了这些信息,函数可以使用简单的指针算法从其堆栈帧中提取参数。