我为什么要包含头文件? #include实际上如何运作?

时间:2012-06-22 13:26:28

标签: c header-files

首先,我在.h文件中编写我的函数,然后将其包含在#include "myheader.h"中。然后,有人说我最好只添加这些文件的函数原型并将实际代码放在一个单独的.c文件中。 现在,我能够编译更多.c文件以仅生成可执行文件,但此时我无法理解为什么我应该添加头文件,如果代码在另一个文件中。

此外,我查看了系统中的标准C库(如stdlib.h),在我看来只存储结构定义,常量和类似...我对C不太好(说实话,stdlib.h对我来说几乎是中文,当然对中文没有冒犯:)),但我没有发现任何一行“操作”代码。但是,我总是只包含它而不添加任何其他东西,我编译我的文件,好像'代码'实际上在那里。

有人可以解释一下这些东西是如何运作的吗?或者,至少,请指点一个好导游?我也搜索了Google和SO,但没有找到任何可以解释清楚的内容。

5 个答案:

答案 0 :(得分:16)

编译C代码时,编译器必须实际知道存在具有已定义名称,参数列表,返回类型和可选修饰符的特定函数。所有这些都被称为函数签名,并且特定函数的存在在头文件中被声明。有了这些信息,当编译器找到对这个函数的调用时,它将知道要查找哪种类型的参数,可以控制它们是否具有适当的类型,并将它们准备到一个结构,该结构将在代码实际执行之前被推送到堆栈跳转到你的函数实现。但是编译器不必知道函数的实际实现,它简单地将对象文件中的“占位符”放到所有函数调用中。 (注意:每个c文件只编译一个目标文件)。 #include simple获取头文件并将#include行替换为文件内容。

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

说完这一切之后,应该清楚为什么你不能将函数 definition 放在头文件中。如果稍后您将#include此标头放入多个c文件中,则它们都会将函数实现编译为两个单独的目标文件。编译器可以很好地工作,但是当链接器想要将所有内容链接在一起时,它会找到该函数的两个实现并且会给你一个错误。

stdlib.h和朋友以同样的方式工作。在它们中声明的函数的实现可以在框架库中找到,即使您不了解它,编译器也会“自动”链接到您的代码。

答案 1 :(得分:3)

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

这个策略是这样的:函数的签名等(这个东西在C中称为声明)进入一个名为header的特殊文件,每个其他希望使用它们的文件几乎完全包含在文件中的那个标题(实际上,#include指令只是告诉编译器包含标题的文字文本),以便编译器再次看到函数声明。

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

C中使用的策略转移了从编译器到开发人员查找所有依赖关系的任务;这是计算机小巧,愚蠢和缓慢的旧时代的遗产,所以开发人员的这种帮助非常有价值。

虽然这个策略有许多缺点,而且理论上现在可以改变它,但标准不会改变,因为已经用这种风格编写了数十亿字节的代码。

tl; dr:这是70年代的遗产。

答案 2 :(得分:1)

在C中,需要在调用函数之前声明函数。这需要的原因是在70年代,首先解析一个文件的所有符号,然后再解析它实际编译代码只需要太多时间。如果在调用它们之前声明所有函数,则单个解析就足够了。然而,在现代系统中,我们不再面临这些限制,这就是为什么现代语言没有这种要求的原因。

想象一下,您的项目中有2个文件a.c b.c。您实现了要在两个文件中使用的函数foo。您不能只在a.c中定义函数并在b.c中使用它,因为您必须在调用之前声明函数。因此,您需要向void foo();添加一行b.c。但每次在a.c中更改函数的签名时,都必须更改b.c中的声明。为了避免这个问题,C语言中的标准策略是在单独的头文件中声明文件实现的所有函数(在本例中为a.h。然后,所有其他想要使用该代码的文件都包含头文件(所以b.c会使用此:#include "a.h")。

答案 3 :(得分:0)

#include是一个预处理程序指令,它使文件在#include出现的位置以文本方式插入。

当链接包含相同头文件的多个.c文件时,必须注意避免多次包含头文件(以文本方式多次插入头文件)。 #ifndef#define#endif预处理程序指令可用于防止多个包含。

#ifndef MY_FILE_H
#define MY_FILE_H

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

#endif /* !MY_FILE_H */

答案 4 :(得分:0)

  

如果代码在另一个文件中,我无法理解为什么要添加头文件。

头文件包含另一个文件中定义的函数的声明,这对于调用函数正确编译的代码是必需的。

例如,假设我编写以下代码:

int main(void)
{
  double *foo = malloc(sizeof *foo * 10);
  if (foo)
  {
    // do something with foo
    free (foo);
  }
  return 0;
}

malloc是一个标准库函数,它动态分配内存并返回指向它的指针。 malloc的返回类型是void *,其中任何值都可以分配给任何其他指针类型。 free是另一个标准库函数,它释放通过malloc分配的内存,其返回类型为void(IOW,没有返回值)。

但是,编译器不会自动知道mallocfree返回(或不返回);在正确转换函数调用之前,它需要查看当前作用域中两个函数的声明。在C89标准和更早版本中,如果在范围内没有声明的情况下调用函数,则编译器假定函数返回int;由于intdouble *不兼容(如果没有强制转换,您无法直接将其中一个分配给另一个),您将获得“不兼容的分配”诊断。在C99及更高版本下,根本不允许隐式声明。无论哪种方式,编译器都不会翻译代码。

我需要添加一行

#include <stdlib.h>

其中包含mallocfree声明以及文件开头的一些其他内容。

您不希望将函数 definitions (或变量定义)放在头文件中有几个原因。假设您在头a.h中定义了函数foo。您在文件a.ha.c中添加了b.c。每个文件都可以单独编译,但是当你尝试将它们链接在一起构建一个库或可执行文件时,你会从链接器中得到一个“多重定义”错误 - 你最终创建了一个函数的两个独立实例同名,这是禁忌。 变量定义也是如此。

它也不能很好地扩展。如果你将一堆函数放在他们自己的头文件中并将它们包含在一个源文件中,你就可以在一个大的glob中翻译所有这些函数。此外,如果您只更改源文件或一个头文件中的代码,则每次重新编译.c文件时,仍然会重新编译所有内容。通过将每个函数放在它自己的.c文件中,只需重新编译 need 要重新编译的文件,就可以减少总体构建时间。