如何使用“...” (变量)参数?
可能的重复:
什么是符合 C 和 C++ 的可变参数函数?< /a>
我在 printf()
函数中看到了 ...
参数。像 printf
或 scanf
这样的函数到底是如何工作的?他们怎么能有无限的输入值呢?
Possible Duplicate:
What are variadic functions in accordance with C and C++?
I've seen ...
argument in printf()
function. Exactly HOW functions like printf
or scanf
work? How is it that they can have infinite input values?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这依赖于 C 调用约定,即调用者从堆栈中弹出参数。
因为调用者知道有多少个参数(以及它们的大小),所以它必须以某种方式将其传达给被调用者。对于 printf() 和系列,这是通过格式字符串实现的。对于 execv 和 family,这是通过终止 NULL 参数来指示的。
被调用者使用标准头 stdarg.h 中定义的宏从堆栈中读取参数。
根据内存(粗略),您需要定义一个 va_list 类型的变量,该变量使用 va_start 从前面的参数初始化,如下所示:
显然,您必须解析格式字符串才能知道是读取 int 还是 char* ,或双倍,或...
HTH
This relies on the C calling convention, which is that the caller pops the arguments from the stack.
Because it's the caller that knows how many arguments (and what size they are) it must communicate this to the callee in some manner. With printf() and family, this is via the format string. With execv and family, this is indicated by having a terminating NULL parameter.
The callee reads the arguments from the stack using the macros defined in the standard header stdarg.h.
From memory (sketchy), you need to define a variable of type va_list, which is initialised from the preceeding argument using va_start, as in:
Obviously, you have to parse the format string to know whether to read an int, or a char*, or a double, or a ...
HTH
为此,您必须了解一些有关底层函数调用和堆栈上传递的参数的信息。
当您调用函数时,需要在堆栈上写入一些内容,例如返回地址、指向上一个堆栈帧的指针等。在堆栈上写入的另一部分包括函数的参数。在 C 中,参数从右到左压入堆栈(与 Pascal 不同)。这样,函数的第一个参数就位于堆栈上参数列表的顶部。 (这不是标准强制要求的,而是现实中发生的情况)。
现在,
printf
和scanf
的工作方式非常简单。他们得到一个字符串作为第一个参数(它位于堆栈的顶部(我的意思是堆栈上的参数列表,但我只是简单地写在堆栈顶部))。在该字符串的指导下,他们尝试从格式字符串所在的位置(在堆栈上)下面检索其他值。因此,例如,如果您调用:printf
在堆栈上看到的是(堆栈顶部位于左侧,假设int
= 4 个字节,little-endian 和64位地址):所以,它所做的就是读取格式字符串,到达
%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
的相同格式声明函数,这样您也可以对自定义函数添加这种额外的保护。For that you have to know a bit about the underlying function call and argument passing on the stack.
When you call a function, a few things need to be written on the stack, such as return address, pointer to previous stack frame etc. Another part written on the stack consists of the arguments of the function. In C, the arguments are pushed on the stack from right to left (unlike for example Pascal). This way, the first arguments of the function is on the top of the arguments list on the stack. (This is not mandated by the standard, but is what happens in reality).
Now, the way
printf
andscanf
work is quite simple. They get a string as the first argument (which is on the top of the stack (I mean arguments list on the stack, but I write on the top of stack just for short)). Guided by that string, they try to retrieve other values from below where the format string is (on the stack). So, for example, if you call:What
printf
sees on the stack is (top of the stack is on the left, assumingint
= 4 bytes, little-endian and 64-bit addresses):So, what it does is read the format string, reach
%d
, then read 4 bytes from below the address of format string (so it reads the |0x78|0x56|0x34|0x12| part) then sees%c
and reads four character from after that (the |0x61|0x00|0x00|0x00) and interpret it as a character etc. (The reason%c
reads four characters instead of one, is thatchar
(as well asshort
) sent through...
is automatically cast toint
)You should note however that
printf
andscanf
do this blindly. Therefore, if you send adouble
as argument ofprintf
and read%d
, it reads the same number of bytes (4), but interprets the bits as anint
and notdouble
--> What you get is gibberish. Also, if you have many%
s (like%d
,%c
etc) but not enough arguments,printf
andscanf
blindly go over the other stack variables and interpret them as data/pointers.Finally, good compilers nowadays read the format string for you and give you a warning if there is a mismatch between number of expected arguments in the format string and the actual arguments sent (or their types).
gcc
andclang
even let you declare a function as following the same formatting ofprintf
andscanf
, so you can have this added protection on your custom functions as well.以下是一些有用的链接:
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
希望这些对您有所帮助。
另外,请花一些时间在网上搜索这些问题。
Here are some useful links:
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
Hope these help you.
Also, please take some time to search the web for these questions.
va_* 手册
从技术上讲,被调用的函数必须具有关于在每种特定情况下调用它的参数(在 *printf() 的情况下,信息以格式字符串的形式传递)。有了这些信息,函数就可以使用简单的指针算术从其堆栈帧中提取参数。
manual to va_*
Technically the function being called must have the exact information about the arguments it is called with in each particular case (in case of *printf() the information is passed in form of format string). Having such information the function can extract the arguments from its stack frame using trivial pointer arithmetic.