c-包含文件两次时输出错误



[edit]似乎有些人在没有阅读我的帖子的情况下进行评论和投票。评论前请先阅读。例如:如果你认为我不止一次反对包含文件,那么你就错过了我帖子的要点

当一些包含文件被包含两次时,我想通过输出错误来捕获草率的编程。

我将#pragma once用于要包含在其他包含文件中的包含文件。这很管用。

我知道我可以做一个"保护宏"的变体来实现这一点。但是有更好的方法吗?另一个#pragma或gcc编译选项?

这是一个保护宏的示例:

#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN
the entire file
#endif /* !FILE_FOO_SEEN */

这对我有用:

#ifdef FILE_FOO_SEEN
#error "FILE_FOO inappropriately included twice.
#endif /* FILE_FOO_SEEN */
#define FILE_FOO_SEEN
/* Active Code */

[edit]注意:我不是在问为什么需要多次支持包含头文件。这是一种常见的情况,并得到gcc杂注的支持。当我有特定的包含文件时,我会询问这种情况,我不希望这些文件被多次包含。如果有人不止一次包含它们,那么这将是两个操作之一:1)将陷阱更改为#pragma一次,或者2)更改代码以消除依赖关系。

因为有一个特殊的pragma支持多重include,所以我认为有些人可能也有很好的技术来避免这种情况。

〔edit〕添加一些我们使用头文件时遵循的规则:

  1. 每个.c文件都有一个附带的.h文件和函数原型。您可以将这个.h文件视为.c文件的接口。该.h文件还可能包含函数使用或返回的结构定义和枚举。这个.h文件不需要包含在任何其他.h文件中,因为其他.h文件不使用函数。

  2. 我们不会为包含main()的.c文件创建配套的.h文件。

  3. 有时,结构和枚举的使用独立于返回或使用它们的函数。对于这些情况,枚举和结构将移动到一个独立的.h文件中。这个.h文件将包含一个#pragma once,因为它可能被多次包含。

  4. 优选不透明结构。

  5. 用于记录接口的Doxygen文档包含在.h文件中。这样做效果很好,因为.h文件包含了到.c文件的完整接口。

  6. 我们根据需要对这些规则进行例外处理,但在大多数情况下,我们都遵守这些规则。

  7. 遵循这些规则使编写单元测试变得更容易,因为完整的界面很容易识别和理解。

我认为你不应该这么做(如果你决定继续这个方案,请提醒我不要使用你的头)。

然而,如果您从众所周知的机制开始,当一个标头包含两次时,可以防止损坏:

#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN
…the entire file…
#endif /* !FILE_FOO_SEEN */

然后,当文件被包含两次时(正如您在问题中所认识到的),您可以很容易地对其进行调整以生成错误:

#if defined(FILE_FOO_SEEN)
#error File foo.h already included
#else
#define FILE_FOO_SEEN
…the entire file…
#endif /* FILE_FOO_SEEN */

由于#error是标准C的一部分,它应该可以工作——至少可以在编译的输出中获得诊断消息。它应该,但可能不会(去年的Solaris——我看着你!)阻止成功的编译。

根据编译器的不同,您可能能够微调错误处理策略,但坦率地说,(到目前为止)最好只允许多次包含标头。

标头应该是自包含的并且是幂等的。

  • 自包含意味着您不需要显式地包含任何其他内容才能使用标头(您只需编写#include "header.h",它就可以工作了)
  • 幂等意味着,如果通过任何机制将文件包含两次,它都可以安全工作

C标准对标准标题要求这样做。严格地说,<assert.h>不是严格幂等的,但其它的都是幂等的。它们都是自包含的,尽管允许<inttypes.h>包含<stdint.h>

您应该遵循该标准——它提供了很好的使用技巧。


相关问题

我并不声称这是一个完整的列表或一个无限制的链接列表。

  • 我应该在页眉中使用#include吗
  • 如何在C中链接多个实现文件
  • 如何在C中构造#includes
  • 什么是记录"使用模式"的良好参考;。h〃;文件在C
  • C如何管理#include多个源文件之间的关系并创建正确的makefile
  • 拆分头文件和C文件中的大型C程序
  • 重复的typedefs-在C中无效,但在C++中有效

tl;dr您可能无法避免多次包含


更多解释为什么

程序的不同部分包含相同的头是完全正常的。#include想要实现的目标是在使用之前就知道所需的结构和功能。由于同一文件的多次包含会减慢编译速度和循环包含可能导致无限循环,因此发明了#pragma once#ifndef - #define - #endif结构。你可能读到#pragma once已经过时了,但它在任何地方都会被使用。。。

类似的是,#import而不是#include只包含一次文件,但这是罕见的AFAIK,至少在C/C++代码中是这样。

现在,假设我们生成了没有双重包含的干净代码,并向每个可能这样做的开发人员发出警告。现在的问题是:如果不同文件中的多个类需要相同的数据结构,该怎么办?由于您坚持只包含单个文件,因此您需要知道包含文件的顺序。

示例

类A需要来自类B的东西,但两者都需要带结构的Header X。您将在类A之前包含B,并且在类A的文件中包含标头X。如果您现在更改类的依赖关系,则可能需要将标头X include移动到链顶部的另一个类。

现在想象一下,这是一个有很多文件的大项目,甚至有更多独立工作的人。很明显,如果可能的话,没有人想理清依赖关系。所以你只需要使用#pragma once,每个人都很高兴,这基本上就是它的全部

打印错误的可能方法

#warning并非完全独立于平台。

#error停止编译/预处理。

第三种方式应该得到GCC和MSVC的支持:

#pragma message("double include")
#pragma message "double include"

最新更新