旧式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基本上不检查任何东西。 你逃脱了,因为,
-
它发生在您正在使用的特定体系结构和编译器组合上的
sizeof(int) == sizeof(char *)
。 它并没有真正转换任何东西,只是数字32位是32位。 -
当你把所有这些参数放在堆栈上时,它只是将它们推入。当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,那么仍然会打印一个数字,即运行时字符串的地址。