关于编译器构造:即使我们没有在C程序中包含stdio.h,为什么也没有得到编译时错误?

关于编译器构造:即使我们没有在C程序中包含stdio.h,为什么也没有得到编译时错误?

Why don't we get a compile time error even if we don't include stdio.h in a C program?

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

此外,如果我指定sleep(1,1,"xyz")或任意数量的参数,则编译器仍会对其进行编译。
但是奇怪的是,gcc能够在链接时找到该函数的定义,我不知道这怎么可能,因为实际的sleep()函数仅接受单个参数,但是我们的程序提到了三个参数。

1
2
3
4
5
6
7
8
9
10
11
/********************************/
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调用约定将其压入堆栈。无论哪种情况,传递额外的参数都是无害的。被调用的函数将不会使用传入的寄存器,也不会引用堆栈上的其他参数,但是不会发生任何不良情况。

传递较少的参数会带来更多问题。被调用的函数将使用碰巧在适当的寄存器或堆栈位置中发生的任何垃圾,然后可能会出现hijinks。


在经典C语言中,不需要原型即可调用函数。编译器将推断该函数返回一个int并接受未知数量的参数。这在某些体系结构上可能会起作用,但是如果函数返回除int之外的其他值(例如结构),或者存在任何参数转换,则它将失败。

在您的示例中,可以看到睡眠,并且编译器假定像

这样的原型

1
int sleep();

请注意,参数列表为空。在C语言中,这与void不同。这实际上意味着"未知"。如果你在写K


n


n


C将为未知类型猜测int。因此,它可能认为睡眠具有以下原型:

1
int sleep(int);

关于提供多个参数和链接...我不这确实让我感到惊讶。如果确实可行,那么在运行时发生了什么?


n


取决于编译器,但是对于gcc(例如,因为这是您所指的那个),某些标准函数(C和POSIX)都内置了"编译器内在函数"。这意味着编译器附带的编译器库(在这种情况下为libgcc)包含该函数的实现。编译器将允许隐式声明(即使用不带标题的函数),并且链接器将在编译器库中找到实现,因为您可能会将编译器用作链接器前端。

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

或者,gcc支持禁用内部函数使用的选项:-fno-builtin或用于粒度控制的-fno-builtin-function。如果您正在执行诸如构建自制内核或其他类型的金属应用程序之类的操作,还有其他一些选项可能会很有用。


推荐阅读