任何已经工作超过一周的C程序员遇到了因调用printf
而导致的崩溃,其格式说明符多于实际参数,例如:
printf("Gonna %s and %s, %s!", "crash", "burn");
但是,当你将太多参数传递给printf时,是否会发生类似的坏事?
printf("Gonna %s and %s!", "crash", "burn", "dude");
我对x86 / x64程序集的了解让我相信这是无害的,虽然我不相信没有一些我缺少的边缘条件,而且我不知道其他架构。这种情况是否保证是无害的,或者这里是否存在可能导致崩溃的陷阱?
答案 0 :(得分:32)
Online C Draft Standard (n1256),第7.19.6.1节,第2段:
fprintf函数将输出写入stream指向的流,在指向格式的字符串的控制下,指定后续参数如何 转换为输出。如果格式的参数不足,则行为是 未定义。 如果参数保留时格式已用尽,则多余的参数为 评估(一如既往),否则忽略。 fprintf函数何时返回 遇到格式字符串的结尾。
除了*printf()
(显然)之外,所有其他vprintf()
函数的行为与多余的参数相同。
答案 1 :(得分:14)
你可能知道printf函数的原型就像这样
int printf(const char *format, ...);
更完整的版本实际上是
int __cdecl printf(const char *format, ...);
__cdecl
定义了“调用约定”,它与其他内容一起描述了如何处理参数。在这种情况下,它意味着args被推入堆栈,并且堆栈由进行调用的函数清理。
_cdecl
的另一种选择是__stdcall
,还有其他选项。使用__stdcall
约定是将参数压入堆栈并由调用的函数清除。但是,据我所知,__stdcall
函数不可能接受可变数量的参数。这是有道理的,因为它不知道要清理多少堆栈。
长期和短期是在__cdecl
函数的情况下,它可以安全地传递你想要的多个args,因为清理是在进行调用的代码中执行的。如果你以某种方式向__stdcall
函数传递了太多参数,则会导致堆栈损坏。可能发生这种情况的一个例子是,如果你有错误的原型。
有关调用约定的更多信息,请访问维基百科here。
答案 2 :(得分:3)
如果删除堆栈帧,所有参数将被压入堆栈并被删除。此行为独立于特定处理器。 (我只记得没有堆栈的主机,设计在70年代)所以,是的,第二个例子不会失败。
答案 3 :(得分:3)
printf
旨在接受任意数量的参数。然后printf读取格式说明符(第一个参数),并根据需要从参数列表中提取参数。这就是为什么太少的参数崩溃的原因:代码只是开始使用不存在的参数,访问不存在的内存或其他一些坏事。但是由于参数太多,额外的参数将被忽略。格式说明符将使用的参数少于传入的参数。
答案 4 :(得分:-2)
评论: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.