C 为什么要包含头文件?“包含”实际上是如何工作的?

C 为什么要包含头文件?“包含”实际上是如何工作的?,c,header-files,C,Header Files,起初,我在一个.h文件中编写我的函数,然后用#include“myheader.h”将其包括在内。然后,有人告诉我,最好只在这些文件中添加函数原型,并将真正的代码放在单独的.c文件中。 现在,我可以编译更多的.c文件来只生成一个可执行文件,但现在我不明白如果代码在另一个文件中,为什么要添加头文件 此外,我还查看了系统中的标准C库(如stdlib.h),在我看来,它似乎只存储结构定义、常量和类似的。。。我对C不太在行(老实说,stdlib.h对我来说几乎是中国人,当然对中国人来说没有冒犯:),但我

起初,我在一个.h文件中编写我的函数,然后用
#include“myheader.h”
将其包括在内。然后,有人告诉我,最好只在这些文件中添加函数原型,并将真正的代码放在单独的.c文件中。 现在,我可以编译更多的.c文件来只生成一个可执行文件,但现在我不明白如果代码在另一个文件中,为什么要添加头文件

此外,我还查看了系统中的标准C库(如
stdlib.h
),在我看来,它似乎只存储结构定义、常量和类似的。。。我对C不太在行(老实说,stdlib.h对我来说几乎是中国人,当然对中国人来说没有冒犯:),但我没有发现任何一行“有效”代码。然而,我总是只包含它,而不添加任何其他内容,并且我编译我的文件,就好像“代码”实际存在一样

有人能给我解释一下这些东西是怎么工作的吗?或者,至少,给我指一个好的向导?我也在Google上搜索过,但没有找到任何解释清楚的东西。

C语言(与C++一起)使用了一种非常过时的策略,让编译器知道其他地方定义的函数

这种策略是这样的:函数等的签名(在C中称为声明)进入一个名为header的特殊文件,并且希望使用它们的每个其他文件几乎都会将该header包含到该文件中(实际上,
#include
指令只是告诉编译器包含头的文本),以便编译器再次看到函数声明

其他语言以不同的方式解决这个问题:编译器看到所有源代码,并记住已编译类本身的元数据

C中使用的策略将查找所有依赖项的任务从编译器转移到了开发人员身上;这是旧时代遗留下来的,当时计算机很小、很笨、速度很慢,因此开发人员的这种帮助非常有价值

尽管这种策略有许多缺点,而且理论上现在可以修改,但标准不会改变,因为已经有千兆字节的代码以这种方式编写


tl;dr:这是70年代遗留下来的。在C语言中,要求在调用函数之前声明一个函数。之所以要求这样做,是因为在70年代,首先解析一个文件的所有符号,然后第二次解析它以实际编译代码需要花费太多的时间。如果所有函数都在调用之前声明一次解析就足够了,但是在现代系统中,我们不再面临这些限制,这就是为什么现代语言没有这个要求的原因


假设您的项目中有两个文件
a.c
b.c
。您实现了一个函数
foo
,您希望在这两个文件中都使用它。您不能只在
a.c
中定义函数,然后在
b.c
中使用它,因为您必须在调用函数之前声明它。因此您需要添加一行
void foo();
b.c
。但是每次您在
a.c
中更改函数的签名时,您都必须更改
b.c
中的声明。为了避免这个问题,c中的标准策略是在单独的头文件中声明您的文件实现的所有函数(在本例中,
a.h
。头文件随后会被希望使用该代码的所有其他文件包括在内(因此
b.c
将使用以下内容:
#包括“a.h”
)编译C代码时,编译器必须实际知道某个特定函数的存在,该函数具有定义的名称、参数列表、返回类型和可选修饰符。所有这些都称为函数签名,并且在头文件中声明某个特定函数的存在。有了这些信息,当编译器找到ca时对于这个函数,它将知道要查找哪种类型的参数,可以控制它们是否具有适当的类型,并在代码实际跳转到函数实现之前将它们准备到一个将被推送到堆栈的结构中。但是编译器不必知道函数的实际实现,它很简单uts对象文件中的“占位符”用于所有函数调用。(注意:每个c文件编译为一个对象文件)。
#include
simple获取头文件并用文件内容替换
#include

编译后,构建脚本将所有对象文件传递给链接器。链接器将解析所有函数“占位符”找到函数实现的物理位置,将它们放在目标文件、框架库或DLL中。它简单地将函数实现的信息放在所有函数调用中可以找到的位置,这样,当函数调用到达时,程序将知道在何处继续执行

说到这里,应该很清楚为什么不能将函数定义放在头文件中。如果以后您将
#将
此头文件包含到多个c文件中,这两个文件都会将函数实现编译成两个单独的目标文件。编译器可以很好地工作,但是当链接器希望每个事情是这样的,它会找到函数的两个实现,并且会给你一个错误

stdlib.h
和friends的工作方式相同。可以在编译器“自动”链接到代码的框架库中找到其中声明的函数的实现即使您不知道。

一个
#include
是一个预处理器指令,它导致文件以文本方式插入到发生
#include
的位置

链接包含相同h的多个.c文件时
#ifndef MY_FILE_H
#define MY_FILE_H

/* This code will not be included more than once. */

#endif /* !MY_FILE_H */
int main(void)
{
  double *foo = malloc(sizeof *foo * 10);
  if (foo)
  {
    // do something with foo
    free (foo);
  }
  return 0;
}
#include <stdlib.h>