头文件只包含在整个程序中一次?

时间:2015-06-16 16:29:52

标签: c++ c c-preprocessor header-files

我知道这是一个常见的问题,但我仍然无法完全理解它。

在从多个不同的源文件和头文件生成的 C 或C ++程序中,当使用标题保护时,每个头文件是否只包含在整个代码中一次?

之前有人告诉我,头文件(带有包含警卫)只会在一个翻译单元中包含一次,但在整个代码中会多次包含。这是真的吗?

如果它只在整个代码中包含一次,当一个文件希望包含它并且预处理器检测到它已经被包含时,那个希望使用它的文件如何知道它之前包含的代码中的位置?

4 个答案:

答案 0 :(得分:28)

这是一个过程:

source           header   source header header
   \           /        \   |      /   /
    \         /          \  |     /   /
  PREPROCESSOR            PREPROCESSOR
       |                      |
       V                      V
 preprocessed code      preprocessed code
       |                      |
    COMPILER               COMPILER
       |                      |
       V                      V
  object code              object code
             \            /
              \          /
               \        /
                 LINKER
                   | 
                   V
               executable

<强>预处理

#include是第一步。它指示预处理器处理指定的文件,并将结果插入到输出中。

如果A包含BCB包含C,则预处理器的A输出将包含已处理的文本C两次。

这是一个问题,因为它会导致重复的声明。一种补救措施是使用预处理程序变量跟踪是否已包含源代码(也称为标题保护)。

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

第一次,EXAMPLE_H未定义,预处理器将评估ifndef / endif块内的内容。第二次,它将跳过该块。因此处理后的输出更改,定义仅包含一次。

这是很常见的,有些编译器实现的非标准指令更短,不需要选择唯一的预处理器变量:

#pragma once

// header contents

您可以了解您希望C / C ++代码的可移植性以及使用哪个标头防护。

标题保护将确保每个标题文件的内容在翻译单元的预处理代码中最多出现一次。

<强>编译

编译器从预处理的C / C ++生成机器代码。

通常,头文件仅包含声明,而不包括实际定义(也称为实现)。编译器包含一个符号表,用于当前缺少定义的任何内容。

<强>链接

链接器组合了目标文件。它将定义(也称为实现)与对符号表的引用相匹配。

可能是两个目标文件提供了定义,链接器将使用一个。如果您已将可执行代码放入标题中,则会发生这种情况。这通常不会在C中发生,但由于模板的原因,它在C ++中经常发生。

标题“代码”,无论是声明还是定义,都包含在所有目标文件中多次,但链接器将所有这些文件合并在一起,因此它只在可执行文件中出现一次。 (我排除了多次出现的内联函数。)

答案 1 :(得分:16)

A&#34;头文件&#34;实际上是在编译开始之前由预处理器插入的。只需将其视为&#34;替换&#34;它的#include指令。

警卫......

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

...在更换后执行。因此,标题实际上可能被多次包含,但是#34;守卫&#34;部分文本只由预处理器传递给编译器一次。

因此,如果标题中有任何代码生成定义,它们当然会包含在编译单元的目标文件中(又名&#34;模块&#34; )。如果多个模块中的标题为#include,则会多次出现这些标题。

对于static定义,这根本不是问题,因为这些在模块之外是不可见的(又名文件范围)。对于程序全局定义,这是不同的,并将导致多个定义&#34;错误。

注意:这主要是针对 C 。对于C ++,存在显着差异,因为类等会给允许多个全局对象的时间/添加额外的复杂性。

答案 2 :(得分:7)

具有相应include guards的头文件仅包含每个翻译单元一次。严格地说,它可能多次包含,但在后续包含中将跳过预处理器#ifndef#endif之间的部分。如果操作正确,则应该是文件的全部(或大部分)。

翻译单元通常对应于“源文件”,尽管一些不起眼的实现可能使用不同的定义。如果单独编译的源文件包含相同的标头,则预处理器无法知道另一个文件已包含它,或者任何其他文件是同一项目的一部分。

请注意,当您将多个源文件(翻译单元)链接到一个二进制文件中时,如果标题不仅包含声明,模板,函数,则可能会遇到多个定义的问题标记为inline的定义或静态变量定义。为了避免这种情况,您应该在标题中声明函数并在单独的源文件中定义它们,您将其与其他源文件链接在一起。

答案 3 :(得分:5)

每个翻译单元将包含一次头文件,是的。每个程序可以包含多次,因为每个翻译单元都是为编译过程单独处理的。它们在链接过程中汇集在一起​​,形成一个完整的程序。