向 printf 传递太多参数

发布于 2024-09-16 04:06:27 字数 360 浏览 2 评论 0原文

任何已经工作了一周以上的 C 程序员都遇到过由于使用比实际参数更多的格式说明符调用 printf 导致的崩溃,例如:

printf("Gonna %s and %s, %s!", "crash", "burn");

但是,当您通过printf 的参数太多?

printf("Gonna %s and %s!", "crash", "burn", "dude");

我对 x86/x64 汇编的了解使我相信这是无害的,尽管我不相信我没有遗漏某些边缘条件,而且我对其他架构一无所知。这种情况是否保证是无害的,或者这里是否也存在潜在的导致崩溃的陷阱?

Any C programmer who's been working for more than a week has encountered crashes that result from calling printf with more format specifiers than actual arguments, e.g.:

printf("Gonna %s and %s, %s!", "crash", "burn");

However, are there any similar bad things that can happen when you pass too many arguments to printf?

printf("Gonna %s and %s!", "crash", "burn", "dude");

My knowledge of x86/x64 assembly leads me to believe that this is harmless, though I'm not convinced that there's not some edge condition I'm missing, and I have no idea about other architectures. Is this condition guaranteed to be harmless, or is there a potentially crash-inducing pitfall here, too?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(5

奶茶白久 2024-09-23 04:06:27

在线 C 草案标准 (n1256),部分7.19.6.1,第 2 段:

fprintf 函数将输出写入到stream 所指向的流中,并受format 所指向的字符串的控制,该字符串指定了后续参数的格式
转换为输出。如果格式参数不足,则行为为
不明确的。 如果格式已用尽而参数仍然存在,则多余的参数将被
评估(一如既往),但会被忽略。
fprintf 函数在以下情况下返回:
遇到格式字符串的结尾。

vprintf() 之外(显然),所有其他 *printf() 函数的行为与多余参数相同。

Online C Draft Standard (n1256), section 7.19.6.1, paragraph 2:

The fprintf function writes output to the stream pointed to by stream, under control of the string pointed to by format that specifies how subsequent arguments are
converted for output. If there are insufficient arguments for the format, the behavior is
undefined. If the format is exhausted while arguments remain, the excess arguments are
evaluated (as always) but are otherwise ignored.
The fprintf function returns when
the end of the format string is encountered.

Behavior for all the other *printf() functions is the same wrt excess arguments except for vprintf() (obviously).

像你 2024-09-23 04:06:27

您可能知道 printf 函数的原型如下所示

int printf(const char *format, ...);

更完整的版本实际上是

int __cdecl printf(const char *format, ...);

__cdecl 定义了“调用约定”,它与其他内容一起描述了如何处理参数。在这种情况下,这意味着 args 被推入堆栈,并且堆栈由进行调用的函数清理。

_cdecl 的一种替代方法是 __stdcall,还有其他方法。对于__stdcall,约定是参数被推入堆栈并由调用的函数清理。但是,据我所知, __stdcall 函数不可能接受可变数量的参数。这是有道理的,因为它不知道要清理多少堆栈。

总而言之,在 __cdecl 函数的情况下,可以安全地传递您想要的任意多个参数,因为清理是在调用的代码中执行的。如果您以某种方式将太多参数传递给 __stdcall 函数,则会导致堆栈损坏。可能发生这种情况的一个例子是如果您使用了错误的原型。

有关调用约定的更多信息可以在维基百科此处找到。

You probably know the prototype for the printf function as something like this

int printf(const char *format, ...);

A more complete version of that would actually be

int __cdecl printf(const char *format, ...);

The __cdecl defines the "calling convention" which, along with other things, describes how arguments are handled. In the this case it means that args are pushed onto the stack and that the stack is cleaned by the function making the call.

One alternative to _cdecl is __stdcall, there are others. With __stdcall the convention is that arguments are pushed onto the stack and cleaned by the function that is called. However, as far as I know, it isn't possible for a __stdcall function to accept a variable number of arguments. That makes sense since it wouldn't know how much stack to clean.

The long and the short of it is that in the case of __cdecl functions its safe to pass however many args you want, since the cleanup is performed in the code makeing the call. If you were to somehow pass too many arguments to a __stdcall function it result in a corruption of the stack. One example of where this could happen is if you had the wrong prototype.

More information on calling conventions can be found on Wikipedia here.

長街聽風 2024-09-23 04:06:27

如果移除堆栈帧,则所有参数将被压入堆栈并被移除。此行为独立于特定处理器。 (我只记得一个没有堆栈的大型机,设计于 70 年代)所以,是的,第二个例子不会失败。

All the arguments will be pushed on the stack and removed if the stack frame is removed. this behaviour is independend from a specific processor. (I only remember a mainframe which had no stack, designed in 70s) So, yes the second example wont't fail.

两人的回忆 2024-09-23 04:06:27

printf 旨在接受任意数量的参数。然后 printf 读取格式说明符(第一个参数),并根据需要从参数列表中提取参数。这就是为什么太少的参数会崩溃:代码只是开始使用不存在的参数,访问不存在的内存,或者其他一些坏事。但如果参数太多,多余的参数就会被忽略。格式说明符将使用比传入的参数更少的参数。

printf is designed to accept any number of arguments. printf then reads the format specifier (first argument), and pulls arguments from the argument list as needed. This is why too few arguments crash: the code simply starts using non-existent arguments, accessing memory that doesn't exist, or some other bad thing. But with too many arguments, the extra arguments will simply be ignored. The format specifier will use fewer arguments than have been passed in.

素罗衫 2024-09-23 04:06:27

评论: gcc 和 clang 都会产生警告:

$ clang main.c 
main.c:4:29: warning: more '%' conversions than data arguments [-Wformat]
  printf("Gonna %s and %s, %s!", "crash", "burn");
                           ~^
main.c:5:47: warning: data argument not used by format string 
                      [-Wformat-extra-args]
  printf("Gonna %s and %s!", "crash", "burn", "dude");
         ~~~~~~~~~~~~~~~~~~                   ^
2 warnings generated.

Comment: both gcc and clang produce warnings:

$ clang main.c 
main.c:4:29: warning: more '%' conversions than data arguments [-Wformat]
  printf("Gonna %s and %s, %s!", "crash", "burn");
                           ~^
main.c:5:47: warning: data argument not used by format string 
                      [-Wformat-extra-args]
  printf("Gonna %s and %s!", "crash", "burn", "dude");
         ~~~~~~~~~~~~~~~~~~                   ^
2 warnings generated.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文