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

发布于 2024-12-02 21:49:08 字数 460 浏览 2 评论 0原文

可能的重复:
什么是符合 C 和 C++ 的可变参数函数?< /a>

我在 printf() 函数中看到了 ... 参数。像 printfscanf 这样的函数到底是如何工作的?他们怎么能有无限的输入值呢?

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

谁人与我共长歌 2024-12-09 21:49:08

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

因为调用者知道有多少个参数(以及它们的大小),所以它必须以某种方式将其传达给被调用者。对于 printf() 和系列,这是通过格式字符串实现的。对于 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* ,或双倍,或...

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:

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);
}

Obviously, you have to parse the format string to know whether to read an int, or a char*, or a double, or a ...

HTH

踏月而来 2024-12-09 21:49:08

为此,您必须了解一些有关底层函数调用和堆栈上传递的参数的信息。

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

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

printf("%d %c %s\n", 0x12345678, 'a', "str");

printf 在堆栈上看到的是(堆栈顶部位于左侧,假设 int = 4 个字节,little-endian 和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 等)但没有足够的参数,则 printf 和 scanf 盲目地检查其他堆栈变量并将它们解释为数据/指针。

最后,现在优秀的编译器会为您读取格式字符串,并在格式字符串中的预期参数数量与发送的实际参数(或其类型)之间不匹配时向您发出警告。 gccclang 甚至 让您按照 printfscanf 的相同格式声明函数,这样您也可以对自定义函数添加这种额外的保护。

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 and scanf 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:

printf("%d %c %s\n", 0x12345678, 'a', "str");

What printf sees on the stack is (top of the stack is on the left, assuming int = 4 bytes, little-endian and 64-bit addresses):

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

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 that char (as well as short) sent through ... is automatically cast to int)

You should note however that printf and scanf do this blindly. Therefore, if you send a double as argument of printf and read %d, it reads the same number of bytes (4), but interprets the bits as an int and not double --> What you get is gibberish. Also, if you have many %s (like %d, %c etc) but not enough arguments, printf and scanf 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 and clang even let you declare a function as following the same formatting of printf and scanf, so you can have this added protection on your custom functions as well.

一抹苦笑 2024-12-09 21:49:08

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文