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

Header file included only once in entire program?

本文关键字:包含一 程序 文件      更新时间:2023-10-16

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

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

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

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

这是过程:

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包括BC,并且B包括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++中经常发生,因为模板。

标头"code"(无论是声明还是定义)在所有对象文件中多次包含,但链接器将所有这些内容合并在一起,因此它在可执行文件中仅存在一次。 (我排除了多次存在的内联函数。

"头文件"实际上是在编译开始之前由预处理器插入的。只要把它想象成只是"取代"它的#include指令。

守卫...

#ifndef MY_HEADER_H
#define MY_HEADER_H
// the "guarded" part of the header file is here...
#endif

。在替换后执行。因此,标头实际上可以多次包含,但文本的"受保护"部分仅由预处理器传递给编译器一次。

因此,如果标头中有任何代码生成定义,它们当然会包含在编译单元(也称为"模块")的目标文件中。如果在多个模块中#include相同的标头,则这些标头将出现多次。

对于static定义,这完全没有问题,因为这些定义在模块(也称为文件范围)之外是不可见的。对于程序全局定义,这是不同的,将导致"多个定义"错误。

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

具有适当包含保护的头文件将仅包含一次,每个翻译单元将只包含一次。严格来说,它可以多次包含,但预处理器#ifndef#endif之间的部分将在后续包含时跳过。如果操作正确,这应该是文件的全部(或大部分)。

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

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

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