C源文件的好处包括它自己的头文件

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

主要好处是让编译器validation标头及其实现的一致性。 你这样做是因为方便,不是因为它是必需的。 绝对有可能让项目在没有这种包含的情况下正确编译和运行,但从长远来看,它会使项目的维护变得复杂。

如果你的文件没有自己的标题,你可能会意外地遇到函数的前向声明与函数定义不匹配的情况 – 可能是因为你添加或删除了一个参数,并忘了更新标题。 发生这种情况时,依赖于不匹配函数的代码仍然会编译,但调用会导致未定义的行为。 让编译器捕获此错误要好得多,这在源文件包含自己的头文件时会自动发生。

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

因此头文件的源文件需要知道它的义务。 这就是它被包括在内的原因。

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

通过在头文件中编写“契约”,您可以确保其他源文件知道如何调用这些函数,或者更确切地说,您将确保编译器将插入正确的代码并在编译时检查其有效性。

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

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

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

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

所以碰巧你有声明,然后是call \ _调用,然后是实现。 你不必,但你可以。

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

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

 /* 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中的定义不匹配; 返回类型不同。 根据foo.h的声明, main.c调用foo函数,假设它返回一个double

foo.cmain.c是彼此分开编译的。 由于main.c调用foo.h声明的foo ,因此编译成功。 由于foo.c不包含foo.h ,编译器不知道声明和定义之间的类型不匹配,因此它也能成功编译。

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

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

并且,正如在前面的回答中指出的,如果foo.h定义了foo.c使用的任何类型或常量,那么你肯定需要包含它。