我正在尝试了解multiple-include optimization如何与gcc一起使用。 最近,我一直在阅读很多包含标准头文件保护的代码,如此
#ifndef _STDIO_H_
#include <stdio.h>
#endif
我想弄清楚这个结构是否有任何好处。
这是我写的一个例子,以便更好地理解这一点。
那么header1.h
#ifndef _HDR_H_
#define _HDR_H_
#define A (32)
#endif
header2.h
#ifndef _HDR_H_
#define _HDR_H_
#define A (64)
#endif
hdr.c
#include <stdio.h>
#include "header1.h"
#include "header2.h"
int main()
{
printf("%d\n", A);
return 0;
}
请注意,header1.h
和header2.h
都使用相同的包含警戒。正如预期的那样,该程序输出header1.h中定义的A
值;跳过header2.h,因为它使用相同的include guard。
这是我想要了解的内容
#if
指令后立即跳过此文件,即它不必等待匹配的#endif
。这是对的吗?预处理器会注意到这样的头文件,因此如果是头文件 出现在随后的#include指令中,然后定义FOO 被忽略,它不会预处理甚至重新打开文件一秒钟 时间。这被称为多重包含优化。
如果我理解正确,这意味着任何头文件只读一次,即使它被包含多次给定的编译过程。因此,额外包括应用程序代码或头文件中的警卫没有任何好处。
答案 0 :(得分:4)
在解析header2.h的时候,预处理器会跳过这个文件吗?
正如@Sean所说,永远不会跳过header2.h
,但在这种情况下,ifndef ... endif
之间的内容将被忽略。
我可以在上面的例子中添加什么来演示它是如何工作的?
在#define B 123
中的#endif
之后添加一些内容(例如,header2.h
)。现在尝试在main
中访问它。它将是可访问的。
现在,尝试在#endif
之前添加。你会看到,它在`main。
答案 1 :(得分:2)
预处理器永远不会跳过header2.h。它将始终包含它,并且在展开时将忽略#ifndef
块中的内容。
在您的示例中,A
将为32,因为herader2.h中的#define
将永远无法到达。如果达到了,你会得到某种“宏重新定义错误”,因为你有多个#define
为“A”。要解决此问题,您需要#undef
A。
现在大多数编译器都支持#pragma once
指令,以节省你必须在头文件中编写包含警戒。
答案 2 :(得分:2)
预处理器开始阻塞假#if[[n]def]
后面的所有输入,以通过后续的编译步骤。
然而,预处理器会继续读取输入,以跟踪所有条件编译#
- 指令的嵌套深度。
当它找到匹配的#endif
时,它开始阻止输入的位置,它就会停止阻塞。
答案 3 :(得分:2)
解析header2.h时,预处理器会跳过此文件吗?
不会跳过该文件。
我的理解是它在第1行的#if指令之后立即跳过此文件,即它不必等待匹配的#endif。这是正确的吗?
是和否。某些编译器在解析第一个头文件时识别该哨兵宏,如果它在第二个文件中找到它,它将立即停止解析。其他编译器将再次解析标题(寻找匹配的#endif
)。
我可以在上面的示例中添加哪些内容来演示其工作原理?
在哨兵宏内外添加打印信息
#ifdef _HEADER_INCLUDED
#define _HEADER_INCLUDED
...
#pragma message ("inside sentry in " __FILE__ "\n")
#endif //#ifdef _HEADER_INCLUDED
#pragma message ("outside sentry in " __FILE__ "\n")
相关资料:
#pragma once
代替哨兵宏。编译速度更快,因为解析的文件非常少。不用担心宏名称冲突。如果检查到sentry宏,则可以将includes包装,以便不再次加载头文件。这通常用于多次包含多个标头的库标头中。可以以丑陋的代码为代价显着加快编译速度:
#ifndef __LIST_H_
#include "list.h"
#endif
答案 4 :(得分:0)
如果我理解正确,这意味着任何头文件只读一次,即使它被包含多次给定的编译过程。因此,额外包括应用程序代码或头文件中的警卫没有任何好处。
没有gcc编译器只根据规则对它知道安全的文件执行此优化:
开始指令的格式必须为
#ifndef FOO
或
#if !defined FOO [equivalently, #if !defined(FOO)]