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 }
};
我希望这可以帮助您理解为什么在某些情况下可以避免包含守卫!
假设我有一个带有函数定义的头文件“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 }
};
我希望这可以帮助您理解为什么在某些情况下可以避免包含守卫!