可能重复:
What are variadic functions in accordance with C and C++?
我在...
函数中看到printf()
参数。确切地说,printf
或scanf
等功能如何运作?他们如何拥有无限的输入值呢?
答案 0 :(得分:3)
这依赖于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 *,还是double,或者...
HTH
答案 1 :(得分:3)
为此你必须了解一下底层函数调用和在栈上传递的参数。
当你调用一个函数时,需要在栈上写一些东西,比如返回地址,指向前一个栈帧的指针等。写在栈上的另一部分由函数的参数组成。在C中,参数从右到左被推到堆栈上(与例如Pascal不同)。这样,函数的第一个参数位于堆栈上的参数列表的顶部。 (这不是标准规定的,而是实际发生的事情)。
现在,printf
和scanf
的工作方式非常简单。他们得到一个字符串作为第一个参数(位于堆栈顶部(我的意思是堆栈上的参数列表,但我只是简单地写在堆栈顶部))。在该字符串的引导下,他们尝试从格式字符串所在的位置(在堆栈上)检索其他值。所以,例如,如果你打电话:
printf("%d %c %s\n", 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
发送的1}}(以及short
)会自动转换为...
)
但是,您应该注意int
和printf
盲目地这样做。因此,如果您发送scanf
作为double
的参数并读取printf
,则它会读取相同的字节数(4),但会将这些位解释为%d
和不是int
- &gt;你得到的是胡言乱语。此外,如果您有许多double
s(如%
,%d
等)但参数不足,%c
和printf
会盲目地检查其他堆栈变量并将它们解释为数据/指针。
最后,好的编译器现在为你读取格式字符串,如果格式字符串中的预期参数数量与发送的实际参数(或它们的类型)不匹配,则会发出警告。 scanf
和gcc
偶let you声明函数的格式与clang
和printf
相同,因此您可以在自定义函数上添加此保护好。
答案 2 :(得分:0)
以下是一些有用的链接:
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
希望这些可以帮助你。
另外,请花一些时间在网上搜索这些问题。
答案 3 :(得分:0)
从技术上讲,被调用的函数必须具有关于在每种特定情况下调用它的参数的确切信息(在* printf()的情况下,信息以格式字符串的形式传递)。拥有这样的信息,该函数可以使用平凡的指针算法从其堆栈帧中提取参数。