简体   繁体   English

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

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

How does the compiler know the prototype of sleep function or even printf function, when I did not include any header file in the first place? 当我首先没有包含任何头文件时,编译器如何知道sleep函数的原型甚至printf函数?

Moreover, if I specify sleep(1,1,"xyz") or any arbitrary number of arguments, the compiler still compiles it. 此外,如果我指定sleep(1,1,"xyz")或任意数量的参数,编译器仍会编译它。 But the strange thing is that gcc is able to find the definition of this function at link time, I don't understand how is this possible, because actual sleep() function takes a single argument only, but our program mentioned three arguments. 但奇怪的是gcc能够在链接时找到这个函数的定义,我不明白这是怎么回事,因为实际的sleep()函数只接受一个参数,但我们的程序提到了三个参数。

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

Lacking a more specific prototype, the compiler will assume that the function returns int and takes whatever number of arguments you provide. 缺少更具体的原型,编译器将假定函数返回int并获取您提供的任意数量的参数。

Depending on the CPU architecture arguments can be passed in registers (for example, a0 through a3 on MIPS) or by pushing them onto the stack as in the original x86 calling convention. 根据CPU体系结构,参数可以在寄存器中传递(例如,MIPS上的a0到a3),或者像在原始x86调用约定中一样将它们推送到堆栈中。 In either case, passing extra arguments is harmless. 在任何一种情况下,传递额外的参数都是无害的。 The called function won't use the registers passed in nor reference the extra arguments on the stack, but nothing bad happens. 被调用的函数不会使用传入的寄存器,也不会引用堆栈上的额外参数,但没有什么不好的事情发生。

Passing in fewer arguments is more problematic. 传递更少的参数更成问题。 The called function will use whatever garbage happened to be in the appropriate register or stack location, and hijinks may ensue. 被调用的函数将使用发生在适当的寄存器或堆栈位置的任何垃圾,并且可能随之发生hijink。

In classic C, you don't need a prototype to call a function. 在经典C中,您不需要原型来调用函数。 The compiler will infer that the function returns an int and takes a unknown number of parameters. 编译器将推断该函数返回一个int并获取未知数量的参数。 This may work on some architectures, but it will fail if the function returns something other than int, like a structure, or if there are any parameter conversions. 这可能适用于某些体系结构,但如果函数返回int之外的其他内容(如结构)或者有任何参数转换,它将失败。

In your example, sleep is seen and the compiler assumes a prototype like 在您的示例中,可以看到睡眠,编译器会假设原型为

int sleep();

Note that the argument list is empty. 请注意,参数列表为空。 In C, this is NOT the same as void. 在C中,这与void不同。 This actually means "unknown". 这实际上意味着“未知”。 If you were writing K&R C code, you could have unknown parameters through code like 如果你正在编写K&R C代码,你可以通过代码来获得未知参数

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

This is all dangerous, especially on some embedded chips where the way parameters are passed for a unprototyped function differs from one with a prototype. 这一切都很危险,特别是在一些嵌入式芯片上,其中为非原型函数传递参数的方式与原型不同。

Note: prototypes aren't needed for linking. 注意:链接不需要原型。 Usually, the linker automatically links with a C runtime library like glibc on Linux. 通常,链接器会自动链接到Linux上的glibc之类的C运行时库。 The association between your use of sleep and the code that implements it happens at link time long after the source code has been processed. 您使用sleep和实现它的代码之间的关联发生在源代码处理很久之后的链接时间。

I'd suggest that you use the feature of your compiler to require prototypes to avoid problems like this. 我建议您使用编译器的功能来要求原型以避免这样的问题。 With GCC, it's the -Wstrict-prototypes command line argument. 使用GCC,它是-Wstrict-prototypes命令行参数。 In the CodeWarrior tools, it was the "Require Prototypes" flag in the C/C++ Compiler panel. 在CodeWarrior工具中,它是C / C ++编译器面板中的“Require Prototypes”标志。

C will guess int for unknown types. C将猜测未知类型的int。 So, it probably thinks sleep has this prototype: 所以,它可能认为睡眠有这个原型:

int sleep(int);

As for giving multiple parameters and linking...I'm not sure. 至于提供多个参数和链接......我不确定。 That does surprise me. 这让我感到惊讶。 If that really worked, then what happened at run-time? 如果真的有用,那么在运行时会发生什么?

This is to do with something called 'K & RC' and 'ANSI C'. 这与称为“K&RC”和“ANSI C”的事情有关。 In good old K & RC, if something is not declared, it is assumed to be int. 在旧的K&RC中,如果没有声明某些东西,则假定它是int。 So any thing that looks like a function call, but not declared as function will automatically take return value of 'int' and argument types depending on the actuall call. 因此,任何看起来像函数调用但未声明为函数的东西都将根据actuall调用自动获取'int'和参数类型的返回值。

However people later figured out that this can be very bad sometimes. 然而人们后来发现有时这可能非常糟糕。 So several compilers added warning. 因此,一些编译器添加了警告。 C++ made this error. C ++犯了这个错误。 I think gcc has some flag ( -ansic or -pedantic? ) , which make this condition an error. 我认为gcc有一些标志(-ansic或-pedantic?),这使得这个条件成为一个错误。

So, In a nutshell, this is historical baggage. 所以,简而言之,这是历史包袱。

Other answers cover the probable mechanics (all guesses as compiler not specified). 其他答案涵盖了可能的机制(所有猜测都没有指定编译器)。

The issue that you have is that your compiler and linker have not been set to enable every possible error and warning. 您遇到的问题是您的编译器和链接器尚未设置为启用所有可能的错误和警告。 For any new project there is (virtually) no excuse for not doing so. 对于任何新项目,(几乎)没有理由不这样做。 for legacy projects more excuse - but should strive to enable as many as possible 遗产项目更多的借口 - 但应努力尽可能多

Depends on the compiler, but with gcc (for example, since that's the one you referred to), some of the standard (both C and POSIX) functions have builtin "compiler intrinsics". 取决于编译器,但是使用gcc(例如,因为那是你提到的那个),一些标准(包括C和POSIX)函数都内置了“编译器内在函数”。 This means that the compiler library shipped with your compiler (libgcc in this case) contains an implementation of the function. 这意味着编译器附带的编译器库(在本例中为libgcc)包含该函数的实现。 The compiler will allow an implicit declaration (ie, using the function without a header), and the linker will find the implementation in the compiler library because you're probably using the compiler as a linker front-end. 编译器将允许隐式声明(即,使用没有头的函数),链接器将在编译器库中找到实现,因为您可能使用编译器作为链接器前端。

Try compiling your objects with the '-c' flag (compile only, no link), and then link them directly using the linker. 尝试使用'-c'标志编译对象(仅编译,无链接),然后使用链接器直接链接它们。 You will find that you get the linker errors you expect. 您会发现您遇到了预期的链接器错误。

Alternatively, gcc supports options to disable the use of intrinsics: -fno-builtin or for granular control, -fno-builtin-function . 或者,gcc支持禁用内在函数的选项: -fno-builtin或粒度控制, -fno-builtin-function There are further options that may be useful if you're doing something like building a homebrew kernel or some other kind of on-the-metal app. 如果您正在构建自制内核或其他类型的金属应用程序,那么还有其他选项可能会有用。

In a non-toy example another file may include the one you missed. 在非玩具示例中,另一个文件可能包括您错过的文件。 Reviewing the output from the pre-processor is a nice way to see what you end up with compiling. 查看预处理器的输出是查看编译结果的好方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM