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

时间:2021-04-10 15:07:59

I know this is a common question but I still can't fully get my head around it.

我知道这是一个常见的问题,但我还是不能完全理解它。

In a C or C++ program generated from multiple different source and header files, will each header file be only included once in the entire code when the header guards are used?

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

Someone told me previously that a header file (with include guards) will get included only once in one translation unit but multiple times in the entire code. Is this true?

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

If it gets included only once throughout the entire code, when one file wishes to include it and the preprocessor detects that it has already been included, how does that file that wishes to use it know whereabouts in the code it was previously included ?

如果在整个代码中只包含一次,当一个文件希望包含它,而预处理程序检测到它已经包含,那么希望使用它的文件如何知道它先前包含的代码中的位置?

4 个解决方案

#1


23  

This is the process:

这是一个过程:

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

Preprocessing

预处理

#include is for this first step. It instructs the preprocessor to processes the specified file, and insert the result into the output.

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

If A includes B and C, and B includes C, the preprocessor's output for A will include the processed text of C twice.

如果A包含B和C, B包含C,那么A的预处理器输出将包含C的处理文本两次。

This is a problem, since it will result in duplicate declarations. A remedy is to use preprocessor variables track whether the source code has been included (aka header guards).

这是一个问题,因为它将导致重复的声明。一种补救方法是使用预处理器变量跟踪源代码是否已包含(也称为头保护)。

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

The first time, EXAMPLE_H is undefined, and the preprocessor will evaluate the contents within the ifndef/endif block. The second time, it will skip that block. So the processed output changes, and the definitions are included only once.

第一次,EXAMPLE_H没有定义,预处理程序将评估ifndef/endif块中的内容。第二次,它会跳过那个块。因此,处理后的输出会发生变化,定义只包含一次。

This is so common that there is a non-standard directive implemented by some compilers that is shorter and does not require choosing a unique preprocessor variable:

这是如此普遍,以至于有一个非标准指令由一些编译器实现,它更短并且不需要选择一个唯一的预处理器变量:

#pragma once

// header contents

You can figure out how portable you want your C/C++ code, and which header guard to use.

您可以计算出您希望您的C/ c++代码的可移植性,以及使用哪个头保护。

Headers guards will ensure the contents of each header file are present at most once in the preprocessed code for a translation unit.

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

Compiling

编译

The compiler generates machine code from your preprocessed C/C++.

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

Generally, the header files only include declarations, not the actual definitions (aka implementations). The compiler includes a symbol table for anything that is currently missing an definition.

通常,头文件只包含声明,而不包含实际的定义(即实现)。编译器包含一个符号表,用于当前缺少定义的任何东西。

Linking

链接

The linker combines the object files. It matches up the definitions (aka implementations) with the references to the symbol table.

链接器组合对象文件。它将定义(又名实现)与符号表的引用相匹配。

It may be that two object files provide the definition, and the linker will take one. This happens if you've put executable code in your headers. This generally does not happen in C, but it happens very frequently in C++, because of templates.

可能是两个对象文件提供了定义,链接器将获得一个。如果将可执行代码放在头文件中,就会发生这种情况。这通常不会发生在C中,但是由于模板的原因,它在c++中经常发生。

The header "code", whether declarations or definitions, is included multiple times across all object files but the linker merges all of that together, so that it is only present once in the executable. (I'm excluding inlined functions, which are present multiple times.)

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

#2


16  

A "header file" is actually inserted by the pre-processor before compilation starts. Just think of it as just "replacing" its #include directive.

在编译开始之前,预处理器实际上会插入一个“头文件”。只要把它看作是“替换”它的#include指令就可以了。

The guard ...

警卫…

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

... is executed after the replacement. So, the header may actually be included multiple times, but the "guarded" part of the text is only passed to the compiler once by the preprocessor.

…在替换后执行。因此,标题实际上可能包含多次,但是文本的“保护”部分仅由预处理器传递给编译器一次。

So, if there are any code-generation definitions in the header, they will - of course - be included into the object file of the compilation unit (aka "module"). If the same header is #includeded in multiple modules, these will appear multiple times.

因此,如果头中有任何代码生成定义,那么它们(当然)将被包含到编译单元(即“模块”)的对象文件中。如果同一标题是包含在多个模块中的#,这些标题将出现多次。

For static definitions, this is no problem at all, as these will not be visible beyond the module (aka file scope). For program-global definitions, that is different and will result in "multiple definitions" error.

对于静态定义,这一点都没有问题,因为这些在模块之外是不可见的(也就是文件范围)。对于程序全局定义,这是不同的,将导致“多个定义”错误。

Note: this is mostly for C. For C++, there are significant differences, as classes, etc. add additional complexity to what/when multiple global objects are allowed.

注意:这主要是为了C++,有显著的差异,因为类等增加了更多的复杂性,当多个全局对象被允许的时候。

#3


7  

A header file with appropriate include guards will be included only once per translation unit. Strictly speaking, it may be included multiple times, but the parts between the preprocessor #ifndef and #endif will be skipped on subsequent inclusions. If done correctly, this should be all (or most) of the file.

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

A translation unit usually corresponds to a "source file", although some obscure implementations may use a different definition. If a separately compiled source file includes the same header, the preprocessor has no way of knowing that another file had already included it, or that any other file was part of the same project.

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

Note that when you come to link together multiple source files (translation units) into a single binary, you may encounter problems with multiple definitions if the header does not consist only of declarations, templates, function definitions that are marked inline, or static variable definitions. To avoid this, you should declare functions in the header and define them in a separate source file, which you link together with your other source files.

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

#4


5  

The header file will be included once per translation unit, yes. It can be included multiple times per program, as each translation unit is handled separately for the compile process. They are brought together during the linking process to form a complete program.

头文件将包含在每个翻译单元一次,是的。它可以在每个程序中包含多次,因为每个翻译单元在编译过程中是分开处理的。它们在连接过程中被组合在一起,形成一个完整的程序。

#1


23  

This is the process:

这是一个过程:

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

Preprocessing

预处理

#include is for this first step. It instructs the preprocessor to processes the specified file, and insert the result into the output.

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

If A includes B and C, and B includes C, the preprocessor's output for A will include the processed text of C twice.

如果A包含B和C, B包含C,那么A的预处理器输出将包含C的处理文本两次。

This is a problem, since it will result in duplicate declarations. A remedy is to use preprocessor variables track whether the source code has been included (aka header guards).

这是一个问题,因为它将导致重复的声明。一种补救方法是使用预处理器变量跟踪源代码是否已包含(也称为头保护)。

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

The first time, EXAMPLE_H is undefined, and the preprocessor will evaluate the contents within the ifndef/endif block. The second time, it will skip that block. So the processed output changes, and the definitions are included only once.

第一次,EXAMPLE_H没有定义,预处理程序将评估ifndef/endif块中的内容。第二次,它会跳过那个块。因此,处理后的输出会发生变化,定义只包含一次。

This is so common that there is a non-standard directive implemented by some compilers that is shorter and does not require choosing a unique preprocessor variable:

这是如此普遍,以至于有一个非标准指令由一些编译器实现,它更短并且不需要选择一个唯一的预处理器变量:

#pragma once

// header contents

You can figure out how portable you want your C/C++ code, and which header guard to use.

您可以计算出您希望您的C/ c++代码的可移植性,以及使用哪个头保护。

Headers guards will ensure the contents of each header file are present at most once in the preprocessed code for a translation unit.

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

Compiling

编译

The compiler generates machine code from your preprocessed C/C++.

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

Generally, the header files only include declarations, not the actual definitions (aka implementations). The compiler includes a symbol table for anything that is currently missing an definition.

通常,头文件只包含声明,而不包含实际的定义(即实现)。编译器包含一个符号表,用于当前缺少定义的任何东西。

Linking

链接

The linker combines the object files. It matches up the definitions (aka implementations) with the references to the symbol table.

链接器组合对象文件。它将定义(又名实现)与符号表的引用相匹配。

It may be that two object files provide the definition, and the linker will take one. This happens if you've put executable code in your headers. This generally does not happen in C, but it happens very frequently in C++, because of templates.

可能是两个对象文件提供了定义,链接器将获得一个。如果将可执行代码放在头文件中,就会发生这种情况。这通常不会发生在C中,但是由于模板的原因,它在c++中经常发生。

The header "code", whether declarations or definitions, is included multiple times across all object files but the linker merges all of that together, so that it is only present once in the executable. (I'm excluding inlined functions, which are present multiple times.)

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

#2


16  

A "header file" is actually inserted by the pre-processor before compilation starts. Just think of it as just "replacing" its #include directive.

在编译开始之前,预处理器实际上会插入一个“头文件”。只要把它看作是“替换”它的#include指令就可以了。

The guard ...

警卫…

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

... is executed after the replacement. So, the header may actually be included multiple times, but the "guarded" part of the text is only passed to the compiler once by the preprocessor.

…在替换后执行。因此,标题实际上可能包含多次,但是文本的“保护”部分仅由预处理器传递给编译器一次。

So, if there are any code-generation definitions in the header, they will - of course - be included into the object file of the compilation unit (aka "module"). If the same header is #includeded in multiple modules, these will appear multiple times.

因此,如果头中有任何代码生成定义,那么它们(当然)将被包含到编译单元(即“模块”)的对象文件中。如果同一标题是包含在多个模块中的#,这些标题将出现多次。

For static definitions, this is no problem at all, as these will not be visible beyond the module (aka file scope). For program-global definitions, that is different and will result in "multiple definitions" error.

对于静态定义,这一点都没有问题,因为这些在模块之外是不可见的(也就是文件范围)。对于程序全局定义,这是不同的,将导致“多个定义”错误。

Note: this is mostly for C. For C++, there are significant differences, as classes, etc. add additional complexity to what/when multiple global objects are allowed.

注意:这主要是为了C++,有显著的差异,因为类等增加了更多的复杂性,当多个全局对象被允许的时候。

#3


7  

A header file with appropriate include guards will be included only once per translation unit. Strictly speaking, it may be included multiple times, but the parts between the preprocessor #ifndef and #endif will be skipped on subsequent inclusions. If done correctly, this should be all (or most) of the file.

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

A translation unit usually corresponds to a "source file", although some obscure implementations may use a different definition. If a separately compiled source file includes the same header, the preprocessor has no way of knowing that another file had already included it, or that any other file was part of the same project.

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

Note that when you come to link together multiple source files (translation units) into a single binary, you may encounter problems with multiple definitions if the header does not consist only of declarations, templates, function definitions that are marked inline, or static variable definitions. To avoid this, you should declare functions in the header and define them in a separate source file, which you link together with your other source files.

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

#4


5  

The header file will be included once per translation unit, yes. It can be included multiple times per program, as each translation unit is handled separately for the compile process. They are brought together during the linking process to form a complete program.

头文件将包含在每个翻译单元一次,是的。它可以在每个程序中包含多次,因为每个翻译单元在编译过程中是分开处理的。它们在连接过程中被组合在一起,形成一个完整的程序。