旧式C函数声明

这是一个简单的函数delcared并使用旧样式语法定义:

#include  void error(message,a1,a2,a3,a4,a5,a6,a7) char *message; char *a1,*a2,*a3,*a4,*a5,*a6,*a7; { fprintf(stderr,message,a1,a2,a3,a4,a5,a6,a7); } int main () { error("[ERROR %d]: %s.\n",110,"Connection timed out"); return 0; } 

它可以编译并正确运行以进行打印:

[ERROR 110]:连接超时。

我读到这个样式没有相关的原型,但是如何在运行时自动将int转换为char *,甚至提供的参数少于声明的参数?

基本上,它的工作原理是因为它太愚蠢而无法更好地了解。 老式的K&R C基本上不检查任何东西。 你逃脱了,因为,

  1. 它发生在您正在使用的特定体系结构和编译器组合上的sizeof(int) == sizeof(char *) 。 它并没有真正转换任何东西,只是数字32位是32位。

  2. 当你把所有这些参数放在堆栈上时,它只是将它们推入。当printf使用它们时,它只是在需要时使用它们而剩下的就是其余部分; 然后,当呼叫返回时,它们就会消失,没有人会更聪明。 但是,如果您碰巧尝试打印七个值,而您只传递六个参数,它会在运行时爆炸,有时会以创造性和意想不到的方式爆炸。

传递太少的参数或错误的类型(您已经完成了两者)会导致未定义的行为。 这正是您不应该在新代码中使用旧样式语法的原因。 如果您使用了新语法,您将从函数定义中获得“免费”原型。 换一种说法:

 void error(char * message, char * a1, char * a2, char * a3, char * a4, char * a5, char * a6, char * a7) { } 

也是一个原型。

使用旧语法,您必须提供自己的语法,而不是。 这意味着编译器无法检查调用。

在实践中(在您的机器上), error是将堆栈中的int读入char * 。 然后,它将char *传递给fprintf 。 但是使用%d说明符,因此fprintf弹出为int 。 这是未定义的行为。 但它碰巧适用于您的机器; char *int的大小可能相同。

error还会从堆栈中读取5个垃圾char *值。 然后它将这些传递给fprintf ,它忽略了因为只有两个转换说明符。

实际上,如果编译器遇到函数error()的定义之前遇到它的使用,编译器就会有效地在范围内有原型(这就是为什么老C程序员经常根据文件的使用顺序在文件中命令函数定义的原因)。 因此, 110可以转换为(char*)110 (而不是在sizeof(int) == sizeof(char*) )的机器上。 看看在sizeof(int) != sizeof(char*)的机器上会发生什么会很有趣。

实际上有一个110到int的转换,这个转换是由fprintf完成的,当fprint读取“%d”时,它会尝试将相应的参数转换为int。 另一点是函数需要指针,即内存地址,指针是整数。 如果传递了一个字符串而不是110,那么仍然会打印一个数字,即运行时字符串的地址。