【问题标题】:How are circular #includes resolved?循环#includes 如何解决?
【发布时间】:2010-06-27 11:11:12
【问题描述】:

在 c 中假设我们有 2 个文件

1.h

#include<2.h>

blah blah

我们有 2.h

#include<1.h>

code

这是怎么解决的??

【问题讨论】:

  • 基本上没有解决。编译器尝试编译一个 .cpp 文件,每次遇到#include 时,它都会插入包含的文件,并开始解析它。如果你确实有一个循环包含(并且没有包含保护),你会得到一个无限大的代码序列(或者更确切地说,编译器迟早会放弃并报告错误)

标签: c++ c


【解决方案1】:

通常,您使用与文件名相对应的 ifndef/define 来保护您的包含文件。这不会阻止文件再次被包含,但会阻止内容(在 ifndef 内)被使用并再次触发递归包含。

 #ifndef HEADER_1_h
 #define HEADER_1_h

 #include "2.h"

 /// rest of 1.h

 #endif

 #ifndef HEADER_2_h
 #define HEADER_2_h

 #include "1.h"

 //  rest of 2.h

 #endif

【讨论】:

  • @Ram - 我想随着事情的重新定义,你会遇到很多错误。
  • 尽管您不应该以 __ 开头包含保护名称。这样的名字是保留的。
  • @tvanfosson:它们是保留的。这是标准中使用的精确词。它说这些名称是“为实现而保留的”。因此,尽管您可能会争辩说“只要不引起冲突就可以使用它们”,但问题是任何实现都可以随时决定使用该名称,然后冲突是 你的 错和你的问题。 :) 任何包含双下划线、任何以下划线开头后跟大写字符以及任何以命名空间范围内的下划线开头的内容,都保留供实现使用。
  • @tvanfosson 不,它们实际上是保留的,至少在 C++ 中是这样。标准。 17.4.3.1.2:“某些名称集和函数签名始终保留给实现: - 每个包含双下划线 _ 或以下划线后跟大写字母 (2.11) 的名称都保留给实现任何用途。”
  • 它们也保留在 C 中; C99, 7.1.3:“所有以下划线和大写字母或另一个下划线开头的标识符始终保留用于任何用途。”
【解决方案2】:

好的,为了完整起见,我将首先引用 tvanfosson 的回答:

你应该使用包含保护:

// header1.hpp
#ifndef MYPROJECT_HEADER1_HPP_INCLUDED
#define MYPROJECT_HEADER1_HPP_INCLUDED

/// Put your stuff here

#endif // MYPROJECT_HEADER1_HPP_INCLUDED

但是包含保护并不是为了解决循环依赖问题,它们是为了防止多重包含,这是完全不同的。

          base.h
        /        \
    header1.h  header2.h
        \        /
         class.cpp

在这种情况下(很常见),您只希望 base.h 包含一次,这就是 include 守卫为您提供的。

所以这将有效地防止双重包含...... 但你将无法编译!

可以通过尝试像编译器那样推理来说明问题:

  • 包含“header1.hpp” > 这定义了包含保护
  • 包括“header2.hpp”
  • 尝试包含“header1.hpp”但不包含,因为包含保护已定义
  • 无法正确解析“header2.hpp”,因为来自“header1.hpp”的类型尚未定义(因为它已被跳过)
  • 返回“header1.hpp”,“header2.hpp”中的类型仍然丢失,因为它们无法编译,因此这里也失败了

最后,你会留下一大堆错误信息,但至少编译器不会崩溃。

解决方案是以某种方式消除对这种循环依赖的需求。

  • 尽可能使用前向声明
  • 将“header1.h”分为两部分:独立于 header2 的部分和其他部分,您只需要将前者包含在 header2 中即可

这将解决循环依赖(手动),编译器中没有任何魔法可以为您解决。

【讨论】:

  • 你错了。当第一次包含 header1.hpp 时,它被跳过,因为尚未定义保护。因此,来自 header1.hpp 的类型已经定义,解析 header2.hpp 没有问题。 (当然,应该避免循环包含。)
  • @PauliL: 不,你真的错了 :) "2.h" 包含在类型定义在 "1.h" 之前,所以在解析两个标题之后,"可能会定义不依赖于“2.h”的 1.h”,但仅此而已。
  • 好的,我想我现在明白你的意思了。导致问题的不是循环include,而是相互依赖的定义。如果 1.h 要求首先包含 2.h,而 2.h 要求首先包含 1.h,则这是行不通的,因为两者都不能是第一个。然后编译器将给出“未定义”错误,因此程序员知道要修复它。
  • 是的。除了来自编译器的消息可能是神秘的(取决于编译器)并且问题来自循环依赖并不是很明显。幸运的是,有外部工具可以发现这种情况:)
  • @ChrisCantrell:包含RandomPoint.h 并在ColorPoint.h 中声明RandomPoint toRandomPoint(ColorPoint) 函数。它应该仍然有效。然后包含ColorPoint.h 并在RandomPoint.h 中声明ColorPoint toColorPoint(RandomPoint)。繁荣。您需要一个循环依赖,即一个依赖于一个依赖于 A 来触发问题的标题 B 的标题 A(并且通过依赖,我的意思是包含 并使用来自的东西)。
【解决方案3】:

既然您在 c++ 标签和 c 下发布了您的问题,那么我假设您使用的是 c++。在 c++ 中,您还可以使用 #pragma once 编译器指令:

1.h:

#pragma once
#include "2.h"
/// rest of 1.h 

2.h:

#pragma once
#include "1.h"
/// rest of 2.h 

结果是一样的。但有一些注意事项:

  1. pragma once 通常会编译得更快一些,因为它是一种更高级别的机制,并且不会像包含守卫那样发生在预处理中

  2. 一些编译器已经知道“处理”#pragma 一次的错误。尽管大多数(如果不是全部)现代编译器都会正确处理它。详细列表见Wikipedia

编辑:我认为 c 编译器也支持该指令,但从未尝试过,此外,在我见过的大多数 c 程序中,包含守卫是标准(可能是由于编译器限制处理 pragma once 指令? )

【讨论】:

  • #pragma once 不仅限于 C++,也没有标准说明预处理器。所以这与“它在某些 C 编译器中受支持”无关,它是哪种语言真的无关紧要:)。
  • 不,在 C++ 中没有 #pragma once 这样的东西。这是一些供应商选择实施的扩展。它不是 C++ 的一部分。 (并且支持它的编译器通常也在 C 中支持它)。顺便说一句,速度是一个神话。今天,编译器使用传统的包含保护实现了相同的优化。
  • 最后一句话不可能是真的。考虑两个系统上所需的元素和验证,很明显#pragma once 更简单、更快。
  • @Panic: 不,编译指示更快 *如果编译器不优化包含防护"。编译器通常会这样做。基本上,如果编译器打开一个文件,会看到它包含一个包含保护,它会在编译后处理它*就像它曾经被包含在 #pragma 中一样。最终结果是它同样快。
  • 我自己希望我们可以在不使用包括守卫或 #pragma once 的情况下逃脱,就像现代语言支持一样。
【解决方案4】:

必须消除圆形夹杂物,而不是用包含保护“解决”(正如公认的答案所暗示的那样)。考虑一下:

1.h:

#ifndef HEADER_1_h
#define HEADER_1_h
#include "2.h"

#define A 1
#define B 2

#endif // HEADER_1_h

2.h:

#ifndef HEADER_2_h
#define HEADER_2_h
#include "1.h"

#if (A == B)
#error Impossible
#endif

#endif // HEADER_2_h

main.c:

#include "1.h"

这将在编译时抛出“Impossible”错误,因为“2.h”由于包含保护而无法包含“1.h”,并且AB都变为0。实际上,这导致难以跟踪的错误,这些错误的出现和消失取决于包含头文件的顺序。

这里正确的解决方案是将AB 移动到“common.h”,然后可以将其包含在“1.h”和“2.h”中。

【讨论】:

    猜你喜欢
    • 2012-03-15
    • 2011-10-19
    • 2015-03-23
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    • 2021-04-26
    相关资源
    最近更新 更多