C include guards是做什么用的?

26

假设我有一个名为 "header.h" 的头文件,其中包含一个函数定义。

#ifndef HEADER_FILE
#define HEADER_FILE

int two(void){
return 2;
}

#endif

这个头文件有一个include guard(包含保护)。但是我对 #define HEADER_FILE 到底在做什么有点困惑。假设我忘记了包含保护,完全可以无视添加 '#define HEADER_FILE'。

当我们定义 HEADER_FILE 时,到底在做什么?我们正在定义什么?为什么忘记包含保护是可以的,在这种情况下我们也可以忘记添加 #define HEADER_FILE?


2
你可能不应该在头文件中包含代码,因为 include guards 仅保护单个翻译单元中的多次包含。在两个分开的源文件中包含该头文件很可能会导致链接时出现双重定义错误。 - paxdiablo
1
哦,代码保护内有一个非静态函数定义。看起来有问题。 - chux - Reinstate Monica
1
这是真的,并值得注意。代码保护措施可防止在单个事务中多次包含,但无法防止在分别编译后随后链接两个不同目标文件时的多次包含。 - Ryan J
在C语言中,这个函数:'int two(void){ return 2; }' 绝对不应该出现在头文件中。而只需要在头文件中声明原型:'int two(void);',实际的函数应该放在.c文件中。 - user3629249
https://en.wikipedia.org/wiki/Include_guard - sancho.s ReinstateMonicaCellio
3个回答

26

这是一个预处理器宏。

这是全部预处理语法,基本上是说,如果这个宏还没有被定义,那就定义它,并包含 #ifndef#endif 之间的所有代码。

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

你的问题:

为什么忘记 include guard 是可以的,在这种情况下我们也可以忘记添加 #define HEADER_FILE?

忘记 include guard 是可以的,因为即使没有它,仍然是合法的 C 代码。预处理器在编译之前处理你的文件,并在你的最终程序中包含指定的代码,如果没有逻辑指定不应该包含。这只是一种常见的做法,但并不是必需的。

一个简单的例子可能有助于说明它的工作原理:

假设你的头文件,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);       
}

一旦进行“预处理”并准备编译,这将被翻译成:

int two(void){
    return 2;
}

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

所有的预编译指令所做的就是确定在 #ifndef ...#endif 之间的头文件内容是否应该替换原始的 #include

然而,由于该函数未被声明为 externstatic,实际上是在头文件中实现的,如果您尝试在另一个源文件中使用它,将会出现问题,因为函数定义不会被包含。


如果我说错了,请纠正我。HEADER_FILE 基本上是一个“宏文件夹”,其中包含 #ifndef 和 #endif 之间所有宏定义的内容。 - Izzo
不,它只是一个符号,就像变量一样,但它只是预处理器语法。使用 #include 包含一个文件实际上只是将文件的内容复制并粘贴到另一个文件中 #include 的位置。在这种情况下,预处理器将看到您先前定义了一个名为 HEADER_FILE 的符号,并确定是否应该再次包含代码。 - Ryan J
你错了 - 预处理器看到HEADER_FILE已经被定义,所以跳过了文件的其余部分(这是一个大的if语句)。 - pm100
好的,我刚刚又做了一些背景阅读。这很有道理。谢谢! - Izzo

9

在这里,你可以防止文件被多次包含。

#ifndef HEADER_FILE

在测试HEADER_FILE未定义时,如果是真的,则执行以下操作:

#define HEADER_FILE

如果你定义了一个文件,现在如果你在另一个文件中包含这个文件,第一次它会定义HEADER_FILE,而第二次,它已经被定义了,因此文件的内容不会再次被包括进来,因为#ifndef HEADER_FILE将是false。
请记住,在实际编译之前,这些都是由预处理器计算出来的,因此它们是在编译时计算的。

2
首先,在现代的C++编译器中,您可以使用#pragma once代替include guards。
然后,您的示例有点混乱,因为您在头文件中定义了一个extern函数。通常,include文件用于定义函数的声明,而不是函数的定义。
如果您在头文件中定义函数,并且该头文件被多个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; }

在最后一个情况下,如果包含此头文件的每个CPP源文件中都定义了函数two(),但是此函数是静态的,因此CPP源文件正确编译,CPP程序也能够正常链接。
在您的问题中,您问道

在哪种情况下我们可以忘记添加#define HEADER_FILE?

个人而言,在非常特殊的棘手情况下,我会使用相同的头文件。
以下两个包含是一个“好”的例子:
/*******************************************************************
* 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的包含如下:

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

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

最初的回答:
第一个包含文件中包含了#pragma once,并且两次调用相同的内部包含文件。
第一次调用是为了定义StructDefineMODULE结构的外部声明。
第二次调用是为了初始化ModuleTRACE结构数组。
由于这个包含文件被调用了两次,所以必须避免使用#pragma once#ifndef
在使用内部包含文件时,我可以百分之百确定用于定义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 }
  };

我希望这可以帮助您理解为什么在某些情况下可以避免使用include guards!"最初的回答"。请看以下内容:

我希望这可以帮助您理解为什么在某些情况下可以避免使用include guards!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接