将太多参数传递给printf。

时间:2022-06-21 22:33:44

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

任何一个已经工作了一个多星期的C程序员都遇到了由于调用printf而导致的崩溃,其格式说明比实际的参数要多。

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时,是否会发生类似的坏事情?

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?

我对x86/x64程序集的了解使我相信这是无害的,尽管我不相信我缺少某些边缘条件,而且我也不知道其他架构。这种情况肯定是无害的吗?或者在这里也有可能引起碰撞的陷阱?

5 个解决方案

#1


13  

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

您可能知道printf函数的原型是这样的。

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.

__cdecl定义了“调用约定”,它与其他东西一起描述了如何处理参数。在本例中,这意味着args被推送到堆栈上,并且该堆栈被执行调用的函数清除。

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.

一个替代_cdecl的是__stdcall,还有其他的。使用__stdcall,约定是将参数推送到堆栈上,并由调用的函数进行清理。但是,据我所知,__stdcall函数不可能接受变量数量的参数。这是有意义的,因为它不知道要清理多少堆栈。

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.

长而短的是,在__cdecl的情况下,它可以安全地传递您想要的很多args,因为在代码中执行了清除操作。如果将太多参数传递给__stdcall函数,则会导致堆栈的损坏。其中一个例子就是如果你有一个错误的原型。

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

更多关于呼叫约定的信息可以在*上找到。

#2


27  

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

在线C标准草案(n1256),第7.19.6.1条第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).

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

#3


3  

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.

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

#4


3  

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.

printf被设计成接受任意数量的参数。printf然后读取格式说明符(第一个参数),并根据需要从参数列表中提取参数。这就是为什么很少有参数崩溃:代码只是开始使用不存在的参数,访问不存在的内存,或者其他一些不好的东西。但是如果有太多的争论,这些额外的争论将会被忽略。格式说明符使用的参数将少于传入的参数。

#5


-2  

Comment: both gcc and clang produce warnings:

注释: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.

#1


13  

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

您可能知道printf函数的原型是这样的。

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.

__cdecl定义了“调用约定”,它与其他东西一起描述了如何处理参数。在本例中,这意味着args被推送到堆栈上,并且该堆栈被执行调用的函数清除。

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.

一个替代_cdecl的是__stdcall,还有其他的。使用__stdcall,约定是将参数推送到堆栈上,并由调用的函数进行清理。但是,据我所知,__stdcall函数不可能接受变量数量的参数。这是有意义的,因为它不知道要清理多少堆栈。

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.

长而短的是,在__cdecl的情况下,它可以安全地传递您想要的很多args,因为在代码中执行了清除操作。如果将太多参数传递给__stdcall函数,则会导致堆栈的损坏。其中一个例子就是如果你有一个错误的原型。

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

更多关于呼叫约定的信息可以在*上找到。

#2


27  

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

在线C标准草案(n1256),第7.19.6.1条第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).

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

#3


3  

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.

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

#4


3  

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.

printf被设计成接受任意数量的参数。printf然后读取格式说明符(第一个参数),并根据需要从参数列表中提取参数。这就是为什么很少有参数崩溃:代码只是开始使用不存在的参数,访问不存在的内存,或者其他一些不好的东西。但是如果有太多的争论,这些额外的争论将会被忽略。格式说明符使用的参数将少于传入的参数。

#5


-2  

Comment: both gcc and clang produce warnings:

注释: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.