C语言标头包括警卫队

示例

几乎每个头文件都应遵循include防护习惯用法:

我的头文件

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// 头文件的代码体

#endif

这样可以确保当您#include "my-header-file.h"在多个位置时,不会得到函数,变量等的重复声明。请想象以下文件层次结构:

头文件1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

标头2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // 做点什么
}

此代码有一个严重的问题:的详细内容MyStruct定义了两次,这是不允许的。由于一个头文件包含另一个头文件,因此这将导致难以跟踪的编译错误。如果您改为使用标头后卫进行操作:

头文件1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

标头2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // 做点什么
}

然后将其扩展为:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // 安全,因为HEADER_1_H是之前#define的。
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // 做点什么
}

当编译器到达header-1.h的第二个包含时,HEADER_1_H先前的包含已经定义了它。因此,归结为以下几点:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // 做点什么
}

因此,没有编译错误。

注意:命名头保护器有多种不同的约定。有些人喜欢命名HEADER_2_H_,有些包括项目名称MY_PROJECT_HEADER_2_H。重要的是要确保遵循的约定使项目中的每个文件都有唯一的头保护。


如果标头中未包含结构详细信息,则声明的类型将为不完整或不透明的类型。此类类型很有用,可以向函数用户隐藏实现细节。在许多方面,FILE标准C库中的类型可以视为不透明类型(尽管它通常不是不透明的,因此标准I / O函数的宏实现可以利用结构的内部结构)。在这种情况下,header-1.h可能包含:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

请注意,该结构必须具有标签名称(在这里MyStruct-位于标签名称空间中,与typedef name的普通标识符名称空间分开MyStruct),并且{ … }省略。这说“有一个结构类型,struct MyStruct并且有一个别名MyStruct”。

在实现文件中,可以定义结构的详细信息以使类型完整:

struct MyStruct {
    …
};

如果使用的是C11,则可以重复typedef struct MyStruct MyStruct;声明而不会导致编译错误,但是C的早期版本会抱怨。因此,即使在此示例中,如果代码仅使用支持C11的编译器进行编译,它仍然是可选的,即使在此示例中,它仍然是最佳选择。


许多编译器支持#pragma once伪指令,其结果相同:

我的头文件

#pragma once

// 头文件代码

但是,#pragma once它不是C标准的一部分,因此如果使用它,则代码的可移植性较差。


一些标头不使用include防护习惯用法。一个特定的例子是标准<assert.h>头。它可以多次包含在一个转换单元中,这样做的效果取决于NDEBUG每次包含标头时是否都定义了宏。您可能偶尔会有类似的要求;这样的情况很少。通常,您的标头应使用include Guard惯用语进行保护。