即使我们在C程序中不包含stdio.h,为什么我们不会得到编译时错误?

当我第一次没有包含任何头文件时,编译器如何知道sleep函数的原型甚至printf函数?

而且,如果我指定sleep(1,1,"xyz")或任意数量的参数,编译器仍会编译它。 但奇怪的是gcc能够在链接时找到这个函数的定义,我不明白这是怎么回事,因为实际的sleep()函数只接受一个参数,但我们的程序提到了三个参数。

 /********************************/ int main() { short int i; for(i = 0; i<5; i++) { printf("%d",i);`print("code sample");` sleep(1); } return 0; } 

缺少更具体的原型,编译器将假定函数返回int并获取您提供的任意数量的参数。

根据CPU体系结构,参数可以在寄存器中传递(例如,MIPS上的a0到a3),也可以像在原始x86调用约定中一样将它们推送到堆栈中。 在任何一种情况下,传递额外的参数都是无害的。 被调用的函数不会使用传入的寄存器,也不会引用堆栈上的额外参数,但没有什么不好的事情发生。

传递更少的参数更成问题。 被调用的函数将使用发生在适当的寄存器或堆栈位置的任何垃圾,并且可能随之发生hijink。

在经典C中,您不需要原型来调用函数。 编译器将推断该函数返回一个int并获取未知数量的参数。 这可能适用于某些体系结构,但如果函数返回除int之外的其他内容(如结构)或者有任何参数转换,它将失败。

在您的示例中,可以看到睡眠,编译器会假设原型为

 int sleep(); 

请注意,参数列表为空。 在C中,这与void不同。 这实际上意味着“未知”。 如果你正在编写K&R C代码,你可以通过代码来获得未知参数

 int sleep(t) int t; { /* do something with t */ } 

这一切都很危险,尤其是在一些嵌入式芯片上,其中为非原型函数传递参数的方式与原型不同。

注意:链接不需要原型。 通常,链接器会自动链接到Linux上的glibc等C运行时库。 您使用sleep和实现它的代码之间的关联发生在源代码处理很久之后的链接时间。

我建议您使用编译器的function来要求原型以避免这样的问题。 使用GCC,它是-Wstrict-prototypes命令行参数。 在CodeWarrior工具中,它是C / C ++编译器面板中的“Require Prototypes”标志。

C将猜测未知类型的int。 所以,它可能认为睡眠有这个原型:

 int sleep(int); 

至于提供多个参数和链接……我不确定。 这让我感到惊讶。 如果真的有用,那么在运行时会发生什么?

这与’K&RC’和’ANSI C’有关。 在旧的K&RC中,如果没有声明某些东西,则假定它是int。 因此,任何看起来像函数调用但未声明为函数的东西都将自动获取’int’和参数类型的返回值,具体取决于actuall调用。

然而人们后来发现有时这可能非常糟糕。 因此,一些编译器添加了警告。 C ++犯了这个错误。 我认为gcc有一些标志(-ansic或-pedantic?),这使得这个条件成为一个错误。

所以,简而言之,这是历史包袱。

其他答案涵盖了可能的机制(所有猜测都没有指定编译器)。

您遇到的问题是您的编译器和链接器尚未设置为启用所有可能的错误和警告。 对于任何新项目,(几乎)没有理由不这样做。 遗产项目更多的借口 – 但应努力尽可能多

取决于编译器,但是使用gcc(例如,因为那是你提到的那个),一些标准(包括C和POSIX)函数都内置了“编译器内在函数”。 这意味着编译器附带的编译器库(在本例中为libgcc)包含该函数的实现。 编译器将允许隐式声明(即,使用不带标头的函数),链接器将在编译器库中找到实现,因为您可能使用编译器作为链接器前端。

尝试使用’-c’标志编译对象(仅编译,无链接),然后使用链接器直接链接它们。 您会发现您遇到了预期的链接器错误。

或者,gcc支持禁用内在函数的选项: -fno-builtin或用于粒度控制, -fno-builtin-function 。 如果您正在构建自制内核或其他类型的金属应用程序,那么还有其他选项可能会有用。

在非玩具示例中,另一个文件可能包括您错过的文件。 查看预处理器的输出是查看最终编译结果的好方法。