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

Eng*_*999 26 c c++ header-files c-preprocessor

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

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

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

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

Pau*_*per 30

这是一个过程:

source           header   source header header
   \           /        \   |      /   /
    \         /          \  |     /   /
  PREPROCESSOR            PREPROCESSOR
       |                      |
       V                      V
 preprocessed code      preprocessed code
       |                      |
    COMPILER               COMPILER
       |                      |
       V                      V
  object code              object code
             \            /
              \          /
               \        /
                 LINKER
                   | 
                   V
               executable
Run Code Online (Sandbox Code Playgroud)

预处理

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

如果A包含BC,并且B包括C,则预处理器的输出A将包括C两次处理的文本.

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

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif
Run Code Online (Sandbox Code Playgroud)

第一次EXAMPLE_H是未定义的,预处理器将评估ifndef/ endifblock中的内容.第二次,它将跳过该块.因此处理后的输出会发生变化,定义只包含一次.

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

#pragma once

// header contents
Run Code Online (Sandbox Code Playgroud)

您可以弄清楚您希望C/C++代码的可移植性以及使用哪个标头防护.

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

编译

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

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

链接

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

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

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


too*_*ite 16

在编译开始之前,预处理器实际插入了"头文件".只需将其视为"替换"其#include指令即可.

守卫 ...

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif
Run Code Online (Sandbox Code Playgroud)

...在更换后执行.因此,标题实际上可能被多次包含,但文本的"保护"部分仅由预处理器传递给编译器一次.

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

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

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

  • 无论如何都值得知道它的存在,而不是作为替代,但有时它可能非常有用,例如:如果你确定它是支持的(你公司的SWE人告诉你)并且你正在一个有数千个标题的项目中工作(编译时间可以加快很多). (5认同)
  • 你也可以提到非标准的`#pragma once`,它值得知道它的存在 (3认同)
  • @dlavila:我没有出于某种目的.一个人不应该依赖它,所以无论如何你必须给卫兵加注.此外,这可能会违反像有意使用多个包含的boost这样的库.(我不是总是坚持标准,但链接一次没有太大的好处,可能会造成很多麻烦). (3认同)

And*_*rew 7

每个翻译单元包含一个带有相应包含警卫的头文件.严格地说,可以包含多次,但预处理器之间的部分,并会在随后的夹杂物跳过.如果正确完成,这应该是文件的全部(或大部分).#ifndef#endif

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

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


小智 5

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