据我所知,如果源文件需要引用其他文件中的函数,那么它需要包含其头文件,但我不明白为什么源文件包含自己的头文件。头文件中的内容只是作为每个处理时间的函数声明被复制并粘贴到源文件中。对于包含自己的头文件的源文件,这样的“声明”对我来说似乎没有必要,实际上,项目在从源文件中删除标题后仍然编译并链接没问题,所以源文件的原因是什么包括它自己的头?
答案 0 :(得分:18)
主要好处是让编译器验证标头及其实现的一致性。你这样做是因为方便,不是因为它是必需的。绝对有可能让项目在没有这样的情况下正确编译和运行,但从长远来看,它会使项目的维护变得复杂。
如果你的文件没有自己的标题,你可能会意外地遇到函数的前向声明与函数定义不匹配的情况 - 可能是因为你添加或删除了一个参数,并忘了更新标题。发生这种情况时,依赖于不匹配函数的代码仍然会编译,但调用会导致未定义的行为。让编译器捕获此错误要好得多,当源文件包含自己的头文件时会自动发生此错误。
答案 1 :(得分:5)
头文件告诉人们源文件可以做什么。
因此头文件的源文件需要知道其义务。这就是为什么它被包括在内。
答案 2 :(得分:4)
你的似乎是一个边缘情况,但是一个包含文件可以被视为该源文件和可能需要这些功能的任何其他源文件之间的一种契约。
写下"合同"在头文件中,您可以确保其他源文件知道如何调用这些函数,或者更确切地说,您将确保编译器将插入正确的代码并在编译时检查其有效性时间。
但是,如果你(甚至无意中)改变了相应源文件中的函数原型怎么办?
通过在该文件中包含与其他人相同的标题,如果在无意中发生变化,您将在编译时被警告" break"合约。
更新(来自@ tmlen的评论):即使在这种情况下它没有,包含文件可能也使用声明和编译指示,如#define,typedef,enum,struct和内联以及编译器宏一样,写入不止一次是没有意义的(实际上,这将是危险在两个不同的地方写入,以免副本彼此不同步而带来灾难性后果)。其中一些(例如structure padding pragma)可能成为难以追踪的错误。
答案 3 :(得分:0)
这很有用,因为函数可以在定义之前声明。
所以碰巧你有声明,然后是call \ _调用,然后是实现。 你不必,但你可以。
头文件包含声明。只要原型匹配,您就可以随时自由调用。只要编译器在完成编译之前找到实现。
答案 4 :(得分:0)
实际示例 - 假设项目中包含以下文件:
/* foo.h */
#ifndef FOO_H
#define FOO_H
double foo( int x );
#endif
/* foo.c */
int foo( int x )
{
...
}
/* main.c */
#include "foo.h"
int main( void )
{
double x = foo( 1 );
...
}
请注意foo.h
中的声明与foo.c
中的声明不符;返回类型不同。根据{{1}}中的声明,main.c
调用foo
函数,假设它返回double
。
foo.h
和foo.c
彼此分开编译。由于main.c
调用了main.c
中声明的foo
,因此它会成功编译。由于foo.h
不包含foo.c
,编译器不知道声明和定义之间的类型不匹配,因此它也可以成功编译。
将两个目标文件链接在一起时,函数调用的机器代码将与函数定义所需的机器代码不匹配。函数调用期望返回foo.h
值,但函数定义返回double
。这是一个问题,特别是如果这两种类型的大小不同。最好的情况是你得到一个垃圾结果。
通过在int
中包含foo.h
,编译器可以在运行程序之前发现这种不匹配。
并且,正如在前面的回答中所指出的,如果foo.c
定义了foo.h
使用的任何类型或常量,那么您肯定需要包含它。