多个包含优化

时间:2013-12-20 13:43:15

标签: c gcc c-preprocessor include-guards

我正在尝试了解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.hheader2.h都使用相同的包含警戒。正如预期的那样,该程序输出header1.h中定义的A值;跳过header2.h,因为它使用相同的include guard。

这是我想要了解的内容

  • 解析header2.h时,预处理器跳过此文件的时间点是什么时候?我的理解是它在第1行的#if指令后立即跳过此文件,即它不必等待匹配的#endif。这是对的吗?
  • 我可以在上面的例子中添加什么来演示它是如何工作的?
编辑:谢谢大家的答案。现在开始变得更有意义了。跟进问题。链接到此帖子第一行的页面包含以下文本

  

预处理器会注意到这样的头文件,因此如果是头文件   出现在随后的#include指令中,然后定义FOO   被忽略,它不会预处理甚至重新打开文件一秒钟   时间。这被称为多重包含优化。

如果我理解正确,这意味着任何头文件只读一次,即使它被包含多次给定的编译过程。因此,额外包括应用程序代码或头文件中的警卫没有任何好处。

5 个答案:

答案 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编译器只根据规则对它知道安全的文件执行此优化:

  1. 控制#if-#endif对之外必须没有令牌,但允许使用空格和注释。
  2. 在控制指令对之外必须没有指令,但是允许使用null指令(除了单个'#'和可能的空格之外的任何行)。
  3. 开始指令的格式必须为

      #ifndef FOO
    

      #if !defined FOO     [equivalently, #if !defined(FOO)]