【问题标题】:What exactly do C include guards do?C 包含守卫究竟是做什么的?
【发布时间】:2015-03-04 19:06:34
【问题描述】:

我有一个关于 C 中包含守卫的问题。我已经阅读了一些内容,但希望能得到一些澄清。

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

#ifndef HEADER_FILE
#define HEADER_FILE

int two(void){
return 2;
}

#endif

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

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

感谢任何帮助!

【问题讨论】:

  • 您可能不应该在头文件中包含代码,因为包含防护仅防止多个包含是单个翻译单元。在两个单独的源文件中包含该头文件可能会导致链接时出现双定义错误。
  • 嗯,代码保护中的非静态函数定义。看起来有问题。
  • 确实如此,值得注意。代码保护防止单个事务中的多次包含,但当两个不同的目标文件分别编译并随后链接时,不能防止多次包含。
  • 在 C 中,这个函数:'int two(void){ return 2; }' 绝不应该在头文件中。而只是原型:'int two(void);'该函数实际上应该在 .c 文件中。

标签: c macros c-preprocessor header-files include-guards


【解决方案1】:

这是一个预处理器宏。

所有这些都是预处理器语法,基本上就是说,如果尚未定义此宏,则定义它并包含 #ifndef#endif 之间的所有代码

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

你的问题:

为什么可以忘记包含保护,在这种情况下我们也可以忘记添加#define HEADER_FILE?

忘记它也没关系,因为没有它它仍然是合法的 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 之间所有内容的所有宏定义?
  • @Teague 不,它只是一个符号,就像一个变量,但它只是预处理器语法。使用#include 包含文件实际上只是获取文件的内容并将其粘贴到另一个文件中的#include 的位置。在这种情况下,预处理器将看到您之前定义了一个名为 HEADER_FILE 的符号,并确定是否应再次包含该代码
  • 你错了——预处理器看到 HEADER_FILE 已经定义,因此跳过文件的其余部分(它是一个很大的 if 语句)
  • 好的,只是做了一些背景阅读。这完全有道理。谢谢!
【解决方案2】:

您可以防止文件被多次包含,这里

#ifndef HEADER_FILE

你测试HEADER_FILE是否没有定义,如果是这样的话

#define HEADER_FILE

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

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

【讨论】:

    【解决方案3】:

    首先,在现代 C++ 编译中,您可以使用 #pragma once 代替包含防护。

    然后,您的示例有点混乱,因为您在标题中定义了 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; }
    

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

    在你的问题中,你问

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

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

    以下 2 个包含是一个“好”的示例:

    /*******************************************************************
    * 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 并调用相同的内部包含 2 次。

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

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

    由于此包含被调用了 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 }
      };
    

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

    【讨论】:

      猜你喜欢
      • 2016-02-19
      • 1970-01-01
      • 1970-01-01
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多