在没有适当原型的情况下调用 printf 会调用未定义的行为吗?
Does calling printf without a proper prototype invoke undefined behavior?
这个看起来无辜的程序是否调用了未定义的行为:
int main(void) {
printf("%d\n", 1);
return 0;
}
是的,在没有适当原型的情况下调用 printf()
(来自标准 header <stdio.h>
或来自正确编写的声明)会调用未定义的行为。
如 C 标准中所述:
6.5.2.2 Function calls
- If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type
float
are promoted to double
. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...
) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
用更务实的话说,在 printf
没有原型的情况下,编译器生成调用序列,就好像 printf
被定义为 int printf(const char *, int)
一样,这可能完全不同并且与标准库中 printf
的实际实现不兼容,定义为 int printf(const char restrict *format, ...)
.
古代 ABI 足够规则,这不会导致问题,但现代(例如 64 位)ABI 使用更高效的调用序列,这使得上面的代码肯定是不正确的。
因此,如果没有 #include <stdio.h>
或至少 printf
的适当原型,这个著名的经典 C 程序也可能会失败:
int main() {
printf("Hello world\n"); // undefined behavior
return 0;
}
C 最初是在将可变数量的参数传递给函数调用不会造成任何困难的平台上实现的,并且如果函数签名,像 foo("Hey", "there", 123);
这样的函数调用将以相同的方式处理是以下任何一项:
int foo(char const *p, char const *q, int x);
int foo(char const *p, char const *q, ...);
int foo(char const *p, ...);
然而,在某些平台上,必须事先知道传递参数的数量和格式;此类平台的 C 实现可以通过将 ...
视为 void*
并让调用代码构造和传递包含所传递参数的结构的参数来适应这一点。在这样的平台上,函数调用可能需要传递p
、q
和x
三个参数,传递p
、q
和地址x
作为三个参数,或者构建一个包含 q
和 x
的结构,然后将 p
连同该结构的地址作为两个参数传递。
如果编译器不知道 ...
在参数列表中的位置,它就无法知道如何格式化参数。
这个看起来无辜的程序是否调用了未定义的行为:
int main(void) {
printf("%d\n", 1);
return 0;
}
是的,在没有适当原型的情况下调用 printf()
(来自标准 header <stdio.h>
或来自正确编写的声明)会调用未定义的行为。
如 C 标准中所述:
6.5.2.2 Function calls
- If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type
float
are promoted todouble
. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...
) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
用更务实的话说,在 printf
没有原型的情况下,编译器生成调用序列,就好像 printf
被定义为 int printf(const char *, int)
一样,这可能完全不同并且与标准库中 printf
的实际实现不兼容,定义为 int printf(const char restrict *format, ...)
.
古代 ABI 足够规则,这不会导致问题,但现代(例如 64 位)ABI 使用更高效的调用序列,这使得上面的代码肯定是不正确的。
因此,如果没有 #include <stdio.h>
或至少 printf
的适当原型,这个著名的经典 C 程序也可能会失败:
int main() {
printf("Hello world\n"); // undefined behavior
return 0;
}
C 最初是在将可变数量的参数传递给函数调用不会造成任何困难的平台上实现的,并且如果函数签名,像 foo("Hey", "there", 123);
这样的函数调用将以相同的方式处理是以下任何一项:
int foo(char const *p, char const *q, int x);
int foo(char const *p, char const *q, ...);
int foo(char const *p, ...);
然而,在某些平台上,必须事先知道传递参数的数量和格式;此类平台的 C 实现可以通过将 ...
视为 void*
并让调用代码构造和传递包含所传递参数的结构的参数来适应这一点。在这样的平台上,函数调用可能需要传递p
、q
和x
三个参数,传递p
、q
和地址x
作为三个参数,或者构建一个包含 q
和 x
的结构,然后将 p
连同该结构的地址作为两个参数传递。
如果编译器不知道 ...
在参数列表中的位置,它就无法知道如何格式化参数。