传递给printf的参数太多了

任何已经工作超过一周的C程序员遇到了因使用更多格式说明符而不是实际参数调用printf而导致的崩溃,例如:

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

但是,当你向printf传递太多参数时,是否会发生类似的坏事?

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

我对x86 / x64程序集的了解使我相信这是无害的,虽然我不相信没有一些我缺少的边缘条件,而且我不知道其他架构。 这种情况是否保证是无害的,或者这里是否存在可能导致崩溃的陷阱?

你可能知道printf函数的原型就像这样

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

实际上是一个更完整的版本

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

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

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

它的长期和短期是在__cdecl函数的情况下,它可以安全地传递你想要的多个args,因为清理是在进行调用的代码中执行的。 如果你以某种方式向__stdcall函数传递了太多参数,则会导致堆栈损坏。 可能发生这种情况的一个例子是,如果你有错误的原型。

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

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

fprintf函数在由格式指向的字符串的控制下将输出写入stream指向的流,该格式指定后续参数如何转换为输出。 如果格式的参数不足,则行为未定义。 如果参数保留时格式已用尽,则会评估多余的参数(一如既往),否则将被忽略。 遇到格式字符串的末尾时,fprintf函数返回。

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

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

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

评论: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.