如何使用"..."(变量)参数?
How to use "..." (variable) argument?
可能的重复项:
根据 C 和 C++,什么是可变参数函数?
我已经printf()
函数中看到...
论点。printf
或scanf
等功能究竟是如何工作的?它们怎么会有无限的输入值?
这依赖于 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 不同(。这样,函数的第一个参数位于堆栈上参数列表的顶部。(这不是标准强制要求的,而是现实中发生的事情(。
现在,printf
和scanf
的工作方式非常简单。他们得到一个字符串作为第一个参数(它位于堆栈的顶部(我的意思是堆栈上的参数列表,但我只是简单地写在堆栈顶部((。在该字符串的指导下,他们尝试从格式字符串所在的下方(在堆栈上(检索其他值。因此,例如,如果您调用:
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
但是,您应该注意,printf
和scanf
盲目地这样做。因此,如果您发送一个double
作为 printf
的参数并读取 %d
,它会读取相同数量的字节 (4(,但将这些位解释为int
而不是double
--> 你得到的是胡言乱语。此外,如果你有很多%
(如%d
、%c
等(但没有足够的参数,printf
和scanf
盲目地检查其他堆栈变量并将它们解释为数据/指针。
最后,现在优秀的编译器会为您读取格式字符串,并在格式字符串中的预期参数数量与发送的实际参数(或其类型(不匹配时向您发出警告。 gcc
和clang
甚至允许您将函数声明为遵循相同的printf
和scanf
格式,因此您也可以在自定义函数上获得这种额外的保护。
以下是一些有用的链接:
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(( 的情况下,信息以格式字符串的形式传递(。有了这些信息,函数可以使用简单的指针算法从其堆栈帧中提取参数。
- 何时应在构造函数参数中使用 const C++?
- 概念可以与模板模板参数一起使用吗?
- 如何在模板参数中使用 std::is_pod?
- 在模板参数中使用 {} 在 type_trait{} 中时,其作用是什么<T>?
- 可变参数模板参数转发使用逗号运算符
- 在模板函数参数中使用 std::bind
- 函数参数中使用的复杂文字'i'
- 设置内核参数时使用CL_INVALID_ARG_SIZE
- 为什么当我尝试将priority_queue与参数一起使用作为指向结构的指针时会弹出错误
- 标准::原子::compare_exchange与两个memory_order参数一起使用的真实示例
- 如何在 C++ 中将 typedef 与类初始值设定项参数一起使用?
- 在命令行参数中使用引号
- 对可变参数模板使用 const 参数
- SFINAE 和模板函数实例化:为什么在启用了 SFINAE 类型的函数参数中使用模板参数时无法推断模板参数?
- 在简单地移动参数时使用函数模板参数的优点
- 如何切片可变参数模板参数并使用它们?
- 我可以将"token pasting operator"与"const"模板参数一起使用吗?
- 哪个强制转换应与模板类参数一起使用,dynamic_cast或reinterpet_cast?
- 在模板参数中使用 ref<>?
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变