C源文件包括自己的头文件有什么好处

What's the benefit for a C source file include its own header file

我知道,如果源文件需要引用其他文件中的函数,则它需要包括其头文件,但是我不明白为什么源文件包括其自己的头文件。 头文件中的内容只是在每个处理时间内作为函数声明被复制并粘贴到源文件中。 对于包含其自己的头文件的源文件,这样的"声明"在我看来似乎不是必需的,事实上,从源文件中删除头后,项目仍然可以编译并且链接没有问题,所以源文件包括它的原因是什么? 自己的标题?


主要好处是让编译器验证标头及其实现的一致性。您这样做是因为它很方便,而不是因为它是必需的。如果没有这样的包含,绝对有可能使项目正确编译并运行,但是从长远来看,这会使项目的维护复杂化。

如果文件不包含其自己的标头,则可能会偶然遇到函数的前向声明与该函数的定义不匹配的情况-可能是因为您添加或删除了一个参数,而忘记了更新标头。发生这种情况时,仍将依赖于具有不匹配功能的代码进行编译,但是调用将导致未定义的行为。最好让编译器捕获此错误,当您的源文件包含其自己的标头时,该错误会自动发生。


头文件告诉人们源文件可以做什么。

因此,头文件的源文件需要知道其义务。这就是为什么包含它。


您的情况似乎是一个边缘情况,但是可以将包含文件视为该源文件与可能需要这些功能的任何其他源文件之间的一种约定。

通过在头文件中编写"合同",可以确保其他源文件将知道如何调用这些函数,或者,可以确保编译器将插入正确的代码并在编译时检查其有效性。 。

但是,如果您随后(甚至无意间)更改了相应源文件中的函数原型,该怎么办?

通过在该文件中包含与其他所有人相同的标头,如果更改无意间"破坏"了合同,您将在编译时被警告。

更新(来自@tmlen的评论):即使在这种情况下不包含,包含文件也可能使用声明和编译指示,例如#defines,typedef,enum,struct和inline以及编译器宏,这在编写时是没有意义的不止一次(实际上,在两个不同的地方写会很危险,以免副本彼此之间失去同步,从而导致灾难性的结果)。其中一些(例如,结构填充实用程序)可能会成为难以跟踪的错误。


实际示例-假设项目中包含以下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 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中的定义不匹配。返回类型不同。 main.c根据foo.h中的声明,假设它返回double来调用foo函数。

foo.cmain.c彼此分开编译。由于main.c调用foo.h中声明的foo,因此编译成功。由于foo.c不包含foo.h,因此编译器不会意识到声明和定义之间的类型不匹配,因此也可以成功编译。

将两个目标文件链接在一起时,用于函数调用的机器代码将与用于函数定义的机器代码不匹配。函数调用期望返回double值,但是函数定义返回int。这是一个问题,尤其是当两种类型的大小不同时。最好的情况是您得到垃圾结果。

通过在foo.c中包含foo.h,编译器可以在运行程序之前捕获此不匹配。

并且,正如先前的答案中指出的那样,如果foo.h定义了foo.c使用的任何类型或常量,那么您肯定需要包括它。


这很有用,因为可以在定义函数之前先声明它们。

因此,碰巧您有一个声明,然后是一个调用调用,然后是实现。
您不必,但是可以。

头文件包含声明。只要原型匹配,您就可以随时调用。并且只要编译器在完成编译之前找到实现。