即使我们在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; }
解决方案
回答
C将猜测int为未知类型。因此,它可能认为睡眠具有以下原型:
int sleep(int);
至于提供多个参数和链接...我不确定。这确实让我感到惊讶。如果确实可行,那么在运行时发生了什么?
回答
取决于编译器,但是对于gcc(例如,由于我们所指的是gcc),某些标准函数(C和POSIX)都内置了"编译器内部函数"。这意味着编译器附带的编译器库(在这种情况下为libgcc)包含该函数的实现。编译器将允许隐式声明(即,使用不带标题的函数),并且链接器将在编译器库中找到实现,因为我们可能会将编译器用作链接器前端。
尝试使用'-c'标志编译对象(仅编译,无链接),然后使用链接器直接链接它们。我们会发现收到预期的链接器错误。
另外,gcc支持禁用内部函数的选项:-fno-builtin或者用于粒度控制的-fno-builtin-function。如果我们正在执行诸如构建自制内核或者其他某种金属上应用程序之类的操作,那么还有其他一些选项可能会很有用。
回答
缺少更具体的原型,编译器将假定函数返回int并接受我们提供的任意数量的参数。
根据CPU体系结构的不同,可以将参数传递到寄存器中(例如,MIPS上的a0到a3),也可以按照原始x86调用约定将其压入堆栈。无论哪种情况,传递额外的参数都是无害的。被调用的函数将不会使用传入的寄存器,也不会引用堆栈上的其他参数,但是不会发生任何不良情况。
传递较少的参数会带来更多问题。调用的函数将使用在适当的寄存器或者堆栈位置中发生的任何垃圾,并且可能会发生hijinks。
回答
在经典C语言中,我们不需要原型即可调用函数。编译器将推断该函数返回一个int并接受未知数量的参数。这在某些体系结构上可能会起作用,但是如果函数返回的不是int之外的其他东西(例如结构),或者存在任何参数转换,它将失败。
在示例中,可以看到睡眠,并且编译器采用如下原型:
int sleep();
请注意,参数列表为空。在C语言中,这与void不同。这实际上意味着"未知"。如果我们正在编写K&R C代码,则可能会通过以下代码获取未知参数:
int sleep(t) int t; { /* do something with t */ }
这是非常危险的,尤其是在某些嵌入式芯片中,为非原型函数传递参数的方式不同于带有原型的参数。
注意:链接不需要原型。通常,链接器会自动与C运行时库(如Linux上的glibc)链接。睡眠的使用与实现睡眠的代码之间的关联发生在处理源代码很长时间之后的链接时间。
我建议我们使用编译器的功能来要求原型,以避免出现此类问题。对于GCC,它是-Wstrict-prototypes命令行参数。在CodeWarrior工具中,它是C / C ++编译器面板中的" Require Prototypes"标志。
回答
在非玩具示例中,另一个文件可能包含我们错过的文件。查看预处理器的输出是查看最终编译结果的好方法。
回答
这与" K&R C"和" ANSI C"有关。
在旧的K&R C中,如果未声明任何内容,则假定为int。
因此,任何看起来像函数调用但未声明为函数的事物
将自动采用'int'和参数类型的返回值,具体取决于
在实际通话中。
但是后来人们发现,有时这可能非常糟糕。所以
几个编译器添加了警告。 C ++出现此错误。我认为gcc有一些
标志(-ansic或者-pedantic?),这会使此条件成为错误。
因此,简而言之,这是历史包g。
回答
其他答案涵盖了可能的机制(未指定编译器时的所有猜测)。
我们遇到的问题是尚未将编译器和链接器设置为启用所有可能的错误和警告。对于任何新项目,(实际上)没有理由不这样做。对于遗留项目,更多借口,但应努力实现尽可能多的支持