先说结论:Include Guard是基于的,而#pragma once是基于文件的。

乍一看,这两个没什么区别,都是为了防止头文件的内容被同一个编译单元包含两次而使用。

但硬要说区别的话就是上面的结论。

  • 实验文件:main.cheader1.hheader2.h
  • 实验命令:gcc -E main.c -o main.i,得到预编译文件
  • 实验目的:观察header2.hheader2_1()函数与header2_2()函数的包含次数
1
2
3
4
5
6
7
8
/* main.c */
#include "header2.h"
#include "header1.h"
int main()
{
return 0;
}

目的非常简单,就是要看结果会包含两个头文件header1.hheader2.h的哪些内容。

1
2
3
4
5
/* header1.h */
void header1() {

}
#include "header2.h"
1
2
3
4
5
6
7
/* header2.h */
void header2_1() {

}
void header2_2() {

}

Step1 正常情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 0 "Main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "Main.c"
# 1 "header2.h" 1
void header2_1() {

}
void header2_2() {

}
# 2 "Main.c" 2
# 1 "header1.h" 1
void header1() {

}
# 1 "header2.h" 1
void header2_1() {

}
void header2_2() {

}
# 5 "header1.h" 2
# 3 "Main.c" 2
int main()
{
return 0;
}

可以发现header2.h被包含了两次。这会导致重定义问题

接下来直接展示Include Guard#pragma once 的区别。

header2.h做具体修改(重复包含保护)

Step2 Include Guard

1
2
3
4
5
6
7
8
9
10
/* header2.h */
void header2_1() {

}
#ifndef _HEADER2_H
#define _HEADER2_H
void header2_2() {

}
#endif
  • 注意我只对header2_2()做了保护

然后预处理得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 0 "Main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "Main.c"
# 1 "header2.h" 1
void header2_1() {

}
void header2_2() {

}
# 2 "Main.c" 2
# 1 "header1.h" 1
void header1() {

}
# 1 "header2.h" 1
void header2_1() { /* 被重复包含 */

}
# 5 "header1.h" 2
# 3 "Main.c" 2
int main()
{
return 0;
}

可以发现header2_1()被重复包含了。毕竟我只对header2_2()部分做了保护

Step3 #pragma once

1
2
3
4
5
6
7
8
/* header2.h */
void header2_1() {

}
#pragma once
void header2_2() {

}
  • 注意预编译指令的位置

然后预处理得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 0 "Main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "Main.c"
# 1 "header2.h" 1
void header2_1() {

}
void header2_2() {

}
# 2 "Main.c" 2
# 1 "header1.h" 1
void header1() {

}
# 3 "Main.c" 2
int main()
{
return 0;
}

可以发现整个header2.h只被包含了一次。


原理分析

Include Guard

预编译器在碰到#ifndef后就会检查宏表是否有相应的宏定义

如果有,就会掠过#ifndef#endif所包含的内容,预编译#endif后面的内容。

#pragma once

预编译器在碰到#pragma once就会标记当前文件,比如本实验中就是header2.h

这样预编译器下一次在别的文件中再次执行#include "header2.h"就会直接跳过不执行。