C include guards到底做了什么?

What exactly do C include guards do?

假设我有一个带有函数定义的头文件“header.h”。

#ifndef HEADER_FILE
#define HEADER_FILE

int two(void){
return 2;
}

#endif

这个头文件有一个包含保护。但是,我对#define HEADER_FILE 实际在做什么感到困惑。假设我忘记了 include 守卫,完全忽略添加 '#define HEADER_FILE' 对我来说是完全合法的。

当我们定义HEADER_FILE时,我们究竟在做什么?我们在定义什么?为什么可以忘记包含防护,在这种情况下我们也可以忘记添加 #define HEADER_FILE?

您在此处防止文件被多次包含

#ifndef HEADER_FILE

你测试一下 HEADER_FILE 是否没有定义,如果是的话那么

#define HEADER_FILE

会定义它,现在如果您将该文件包含在另一个文件中,第一次它将定义 HEADER_FILE,而第二次,它将已经定义,因此文件的内容不是再次包括在内,因为 #ifndef HEADER_FILE 将是错误的。

请记住,这些是在实际编译完成之前由预处理器计算的,因此它们是在编译时计算的。

这是一个预处理器宏。

所有这些都是预处理器语法,基本上就是说,如果这个宏还没有被定义,定义它并包括 #ifndef#endif

之间的所有代码

它的作用是防止多次包含文件,这可能导致您的代码出现问题。

您的问题:

And why is it okay to forget the include guard in which case we can also forgot adding #define HEADER_FILE?

忘记它没关系,因为没有它它仍然是合法的 C 代码。预处理器在编译之前处理您的文件,如果没有逻辑说明为什么不应该在最终程序中包含指定的代码。这只是一种常见的做法,但不是必需的。

一个简单的例子可能有助于说明这是如何工作的:

你的 header 文件,header_file.h 我们会说,包含这个:

#ifndef HEADER_FILE
#define HEADER_FILE

int two(void){
    return 2;
}

#endif

在另一个文件 (foo.c) 中,您可能有:

#include "header_file.h"

void foo() {
    int value = two();
    printf("foo value=%d\n", value);       
}

一旦 "preprocessed" 并准备好编译,这将转换为:

int two(void){
    return 2;
}

void foo() {
    int value = two();
    printf("foo value=%d\n", value);       
}

include guard 在这里完成的所有工作是确定 #ifndef ...#endif 之间的 header 内容是否应该粘贴到原始 #include 的位置.

但是,由于该函数未声明 [​​=20=] 或 static,并且实际上是在 header 文件中实现的,因此如果您尝试使用它就会遇到问题在另一个源文件中,因为不会包含函数定义。

首先,在现代 C++ 编译中,您可以使用 #pragma once 而不是 include guards。

然后,您的示例有点混乱,因为您在 header 中定义了一个 extern 函数。通常 include 文件用于定义函数的声明而不是函数的定义。

如果您在 header 中定义函数,并且如果此 header 被多个 CPP 源文件使用,则此函数将以相同的名称定义多次,并且您会出错程序何时链接 !

更好的包含方式是

#ifndef HEADER_FILE
#define HEADER_FILE

int two(void);

#endif

#ifndef HEADER_FILE
#define HEADER_FILE

static int two(void) { return 2; }

#endif

#pragma once

static int two(void) { return 2; }

在最后一种情况下,函数 two() 在包含此 header 的每个 CPP 源文件中定义;但是这个函数是静态的,所以 CPP 源编译正确,CPP 程序链接没有问题。

在你的问题中,你问

in which case we can also forgot adding #define HEADER_FILE?

就个人而言,我在非常特殊的棘手情况下使用相同的 header。

以下2个是"good"例子:

/*******************************************************************
* XTrace.Configuration.h
********************************************************************
*/

#pragma once

#define MODULEx(n) extern StructDefineMODULE MODULE_##n;

#include "XTrace.Modules.h"

#undef MODULEx

#define MODULEx(n) { #n, &MODULE_##n } ,

static struct ModuleTRACE tModuleTrace[]
= {
#include "XTrace.Modules.h"
  { 0, 0 }
  };

其中 XTrace.Modules.h include 如下

/*******************************************************************
* XTrace.Modules.h
********************************************************************
*/

MODULEx( BBDIXFILE )
MODULEx( CECHO )
MODULEx( INITDBFIELD )
MODULEx( IVIRLUX )

第一个包含包含 #pragma once 并调用相同的内部包含 2 次。

第一次调用定义StructDefineMODULE结构的extern声明

第二次调用初始化一个ModuleTRACE结构数组。

因为这个include被调用了2次,所以必须避免#pragma once#ifndef

在使用内部包含时,我 100% 确定用于定义 StructDefineModule 的所有元素也用于初始化 tModuleTrace[] 数组。

包含内部结果,将是

/*******************************************************************
* XTrace.Configuration.h
********************************************************************
*/

#pragma once

extern StructDefineMODULE MODULE_BBDIXFILE;
extern StructDefineMODULE MODULE_CECHO;
extern StructDefineMODULE MODULE_INITDBFIELD;
extern StructDefineMODULE MODULE_IVIRLUX;

static struct ModuleTRACE tModuleTrace[]
= { { "BBDIXFILE"   , &MODULE_BBDIXFILE }
  , { "CECHO"       , &MODULE_CECHO }
  , { "INITDBFIELD" , &MODULE_INITDBFIELD }
  , { "IVIRLUX"     , &MODULE_IVIRLUX }
  , { 0, 0 }
  };

我希望这可以帮助您理解为什么在某些情况下可以避免包含守卫!