0

我试图了解多重包含优化如何与 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

硬盘驱动器

#include <stdio.h>
#include "header1.h"
#include "header2.h"

int main()
{
     printf("%d\n", A);
     return 0;
}

请注意,两者都header1.h使用header2.h相同的包含防护。正如预期的那样,该程序输出Aheader1.h 中定义的值;header2.h 被跳过,因为它使用相同的包含保护。

这就是我想要理解的

  • 在解析 header2.h 时,预处理器会在什么时候跳过这个文件?我的理解是它#if在第 1 行的指令之后立即跳过该文件,即它不必等待匹配的#endif. 它是否正确?
  • 我可以在上面的示例中添加什么来演示它是如何工作的?

编辑:谢谢大家的回答。这现在开始变得更有意义了。一个后续问题。链接到这篇文章第一行的页面有以下文字

预处理器注意到这样的头文件,因此如果头文件出现在随后的#include 指令中并且定义了 FOO,那么它会被忽略并且它不会预处理甚至第二次重新打开文件。这称为多重包含优化。

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

4

5 回答 5

4

在解析 header2.h 时,预处理器会在什么时候跳过这个文件?

正如@Sean 所说,header2.h永远不会被跳过,但ifndef ... endif在这种情况下,两者之间的内容将被忽略。


我可以在上面的示例中添加什么来演示它是如何工作的?

在in之后添加一些东西(例如, a #define B 123)。现在尝试在. 它将是可访问的。#endifheader2.hmain

现在,尝试. #endif你会看到,它在 `main.

于 2013-12-20T13:51:39.313 回答
2

预处理器永远不会跳过 header2.h。它将始终包含它,并且在扩展时会忽略#ifndef块中的内容。

在您的示例A中将是 32,因为#define永远无法达到 inherader2.h。如果达到它,你会得到某种“宏重新定义错误”,#define因为“A”有多个 s。要解决此问题,您需要#undefA.

如今,大多数编译器都支持该#pragma once指令,以使您不必在头文件中编写包含保护。

于 2013-12-20T13:48:43.747 回答
2

预处理器开始阻塞所有在 false 之后的输入,#if[[n]def]以通过后续的编译器步骤。

然而,预处理器会继续读取输入,以跟踪所有这些条件编译指令的嵌套深度#

当它找到匹配#endif的,它开始阻塞输入的位置时,它就会停止阻塞。

于 2013-12-20T13:55:30.437 回答
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代替哨兵宏。由于解析的文件很少,因此编译速度更快。不用担心宏名称冲突。
  • 您可以将包含 if 检查包装到哨兵宏,这样头文件就不会再次加载。这通常用于多次包含多个头文件的库头文件中。可以以丑陋的代码为代价显着加快编译速度:

    #ifndef __LIST_H_

    #include "list.h"

    #endif

于 2013-12-20T14:13:36.937 回答
0

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

没有 gcc 编译器只对它知道遵循规则安全的文件进行这种优化:

  1. 控制 #if-#endif 对之外必须没有标记,但允许空格和注释。
  2. 控制指令对之外不能有任何指令,但允许使用空指令(一行只包含一个“#”和可能的空格)。
  3. 开场指示必须采用以下形式

      #ifndef FOO
    

    或者

      #if !defined FOO     [equivalently, #if !defined(FOO)]
    
于 2013-12-20T14:27:13.367 回答