75

如果在标题(* .h)内,是否有必要#include使用此文件中定义的类型?

例如,如果我使用 GLib 并希望gchar在头文件中定义的结构中使用基本类型,是否有必要执行 a #include <glib.h>,知道我的 *.c 文件中已经有了它?

如果是,我还必须将它放在#ifndefand之间#define或之后#define

4

9 回答 9

116

NASA 的戈达德太空飞行中心 ( GSFC ) 针对 C 中的头文件规定,必须可以在源文件中包含头文件作为唯一头文件,然后使用该头文件提供的工具的代码将被编译。

这意味着标头必须是自包含的、幂等的和最小的:

  • 自包含的——如果需要,所有必要的类型都通过包含相关标题来定义。
  • 幂等——即使多次包含编译也不会中断。
  • 最小——它没有定义任何代码不需要的任何东西,这些代码使用标头来访问标头定义的设施。

这条规则的好处是,如果有人需要使用标头,他们不必费力找出还必须包含哪些其他标头——他们知道标头提供了所有必要的东西。

可能的缺点是某些标题可能会被包含多次;这就是为什么多个包含标头保护至关重要的原因(以及为什么编译器尽可能避免重新包含标头)。

执行

此规则意味着,如果标头使用一种类型——例如 ' FILE *' 或 ' size_t'——那么它必须确保应该包含适当的其他标头(<stdio.h><stddef.h>例如)。一个经常被遗忘的推论是,标头不应包含包的用户为了使用包而不需要的任何其他标头。换句话说,标题应该是最小的。

此外,GSFC 规则提供了一种简单的技术来确保发生这种情况:

  • 在定义功能的源文件中,标头必须是列出的第一个标头。

因此,假设我们有一个魔术排序。

魔术排序.h

#ifndef MAGICSORT_H_INCLUDED
#define MAGICSORT_H_INCLUDED

#include <stddef.h>

typedef int (*Comparator)(const void *, const void *);
extern void magicsort(void *array, size_t number, size_t size, Comparator cmp);

#endif /* MAGICSORT_H_INCLUDED */

魔术排序.c

#include <magicsort.h>

void magicsort(void *array, size_t number, size_t size, Comparator cmp)
{
    ...body of sort...
}

请注意,标头必须包含一些定义的标准标头size_t;这样做的最小标准标头是<stddef.h>,尽管其他几个也这样做(<stdio.h>, <stdlib.h>, <string.h>,可能还有其他几个)。

另外,如前所述,如果实现文件需要一些其他头文件,那就这样吧,需要一些额外的头文件是完全正常的。但是实现文件('magicsort.c')应该自己包含它们,而不是依赖它的头文件来包含它们。标题应仅包含软件用户需要的内容;不是实施者需要的。

配置标头

如果您的代码使用配置头文件(例如 GNU Autoconf 和生成的“config.h”),您可能需要在“magicsort.c”中使用它:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include "magicsort.h"

...

这是我唯一一次知道模块的私有头不是实现文件中的第一个头。但是,“config.h”的条件包含可能应该在“magicsort.h”本身中。


2011-05-01 更新

上面链接的 URL 不再起作用 (404)。您可以在EverySpec.com找到 C++ 标准 (582-2003-004) ;C 标准 (582-2000-005) 似乎在行动中缺失。

C标准的指导方针是:

§2.1 单位

(1) 代码应以单元或独立头文件的形式构成。

(2) 一个单元应由一个头文件 (.h) 和一个或多个正文 (.c) 文件组成。头文件和正文文件统称为源文件。

(3) 单元头文件应包含客户单元所需的所有相关信息。单元的客户端只需访问头文件即可使用该单元。

(4) 单元头文件应包含单元头所需的所有其他头的#include 语句。这允许客户端通过包含单个头文件来使用一个单元。

(5) 单元主体文件应在所有其他#include 语句之前包含单元头的#include 语句。这让编译器可以验证所有必需的#include 语句是否都在头文件中。

(6) 正文文件应仅包含与一个单元相关的功能。一个正文文件可能不提供在不同标头中声明的函数的实现。

(7) 使用给定单元 U 的任何部分的所有客户端单元应包含单元 U 的头文件;这确保了只有一个地方定义了单元 U 中的实体。客户端单元只能调用单元头中定义的函数;他们可能不会调用在正文中定义但未在标头中声明的函数。客户端单元可能无法访问在正文中声明的变量,但不能访问标头中声明的变量。

一个组件包含一个或多个单元。例如,数学库是包含向量、矩阵和四元数等多个单位的组件。

独立的头文件没有关联的主体;例如,一个通用类型的头文件不声明函数,所以它不需要正文。

一个单元有多个正文文件的一些原因:

  • 部分主体代码取决于硬件或操作系统,但其余部分是常见的。
  • 文件太大。
  • 该单元是一个常用的实用程序包,有些项目只会使用其中的几个功能。将每个函数放在单独的文件中允许链接器从最终图像中排除未使用的函数。

§2.1.1 标题包括基本原理

该标准要求一个单元的标题包含#include该单元标题所需的所有其他标题的声明。在单元主体中首先放置#include单元头允许编译器验证头包含所有必需的#include语句。

本标准不允许的替代设计不允许#include在标题中声明;所有 #includes 都在正文文件中完成。然后单元头文件必须包含#ifdef检查所需头文件是否以正确顺序包含的语句。

替代设计的一个优点是#include主体文件中的列表正是生成文件中所需的依赖项列表,并且该列表由编译器检查。对于标准设计,必须使用工具来生成依赖关系列表。但是,所有分支推荐的开发环境都提供了这样的工具。

替代设计的一个主要缺点是,如果一个单元的所需标题列表发生更改,则必须编辑使用该单元的每个文件以更新#include语句列表。此外,编译器库单元所需的标头列表在不同的目标上可能不同。

替代设计的另一个缺点是必须修改编译器库头​​文件和其他第三方文件以添加所需的#ifdef语句。

另一种常见的做法是在正文文件中将所有系统头文件包含在任何项目头文件之前。本标准不遵循这种做法,因为某些项目头文件可能依赖于系统头文件,或者因为它们使用系统头文件中的定义,或者因为它们想要覆盖系统定义。此类项目头文件应包含#include 系统头文件的语句;如果正文首先包含它们,则编译器不会对此进行检查。

GSFC 标准可通过 Internet 存档 2012-12-10

信息Eric S. Bullington提供:

引用的 NASA C 编码标准可以通过 Internet 存档访问和下载:

http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?TypeAsset=Standard

测序

问题还问:

如果是,我是否还必须将它(#include行)放在#ifndefand之间#define或之后#define

答案显示了正确的机制——嵌套的包含等应该在#define(并且#define应该是标题中的第二个非注释行)之后——但它没有解释为什么这是正确的。

考虑一下如果你把和放在#include之间会发生什么。假设另一个标头本身包括各种标头,甚至可能是间接的。如果第二次包含发生在 之前,则在定义它定义的类型之前,将第二次包含标头。因此,在 C89 和 C99 中,任何类型名称都将被错误地重新定义(C2011 允许将它们重新定义为相同的类型),并且您将获得多次处理文件的开销,从而违背了第一个标头保护的目的地方。这也是为什么 the是第二行而不是写在 . 之前的原因。给出的公式是可靠的:#ifndef#define#include "magicsort.h"magicsort.h#define MAGICSORT_H_INCLUDEDtypedef#define#endif

#ifndef HEADERGUARDMACRO
#define HEADERGUARDMACRO

...original content of header — other #include lines, etc...

#endif /* HEADERGUARDMACRO */
于 2009-11-26T16:43:25.060 回答
22

一个好的做法是仅在包含文件需要时才将#includes 放入包含文件中。如果给定包含文件中的定义仅在 .c 文件中使用,则仅将其包含在 .c 文件中。

在您的情况下,我会将其包含在#ifdef/#endif 之间的包含文件中。

这将最大限度地减少依赖关系,以便在包含文件更改时不必重新编译不需要给定包含的文件。

于 2009-11-26T16:10:41.337 回答
0

我使用以下构造来确保在此包含之前包含所需的包含文件。我只在源文件中包含所有头文件。

#ifndef INCLUDE_FILE_H
 #error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h"
#endif
于 2009-11-26T17:04:46.853 回答
0

通常,库开发​​人员使用 #ifndef /#define / #endif "trick" 来保护他们的包含免受多个包含的影响,因此您不必这样做。

当然,您应该检查...但无论如何编译器会在某个时候告诉您 ;-) 检查多个包含项无论如何都是一个好习惯,因为它会减慢编译周期。

于 2009-11-26T15:57:19.907 回答
0

在编译期间,预处理器只是用指定的文件内容替换#include 指令。为了防止无限循环,它应该使用

#ifndef SOMEIDENTIFIER
#define SOMEIDENTIFIER
....header file body........
#endif

如果某些标头包含在另一个包含在您的文件中的标头中,则无需再次显式包含它,因为它将递归地包含在文件中

于 2009-11-26T16:11:19.057 回答
0

是的,这是必要的,否则编译器在尝试编译它不“知道”的代码时会抱怨。认为#include 是对编译器的提示/轻推/弯头,告诉它为了成功编译而获取声明、结构等。jldupont 指出的#ifdef/#endif 标头技巧是为了加快代码的编译速度。

它用于您拥有 C++ 编译器并编译纯 C 代码的情况,如下所示 这是该技巧的一个示例:

#ifndef __MY_HEADER_H__
#define __MY_HEADER_H__

#ifdef __cplusplus
外部“C”{
#万一


/* 这里是 C 代码,例如结构、声明等 */

#ifdef __cplusplus
}
#万一

#endif /* __MY_HEADER_H__ */

现在,如果它被多次包含,编译器将只包含一次,因为符号__MY_HEADER_H__被定义一次,这加快了编译时间。 请注意上面示例中的符号 cplusplus,如果您有 C 代码,这是处理 C++ 编译的正常标准方法。

我已经包含了以上内容来展示这一点(尽管与海报的原始问题并不真正相关)。希望这会有所帮助,最好的问候,汤姆。

PS:很抱歉让任何人对此投反对票,因为我认为这对 C/C++ 的新手很有用。留下评论/批评等,因为他们是最受欢迎的。

于 2009-11-26T16:13:32.073 回答
0

您需要包含标题中的标题,并且无需将其包含在 .c 中。包含应该在#define 之后,这样就不会不必要地多次包含它们。例如:

/* myHeader.h */
#ifndef MY_HEADER_H
#define MY_HEADER_H

#include <glib.h>

struct S
{
    gchar c;
};

#endif /* MY_HEADER_H */

/* myCode.c */
#include "myHeader.h"

void myFunction()
{
    struct S s;
    /* really exciting code goes here */
}
于 2009-11-26T16:23:13.447 回答
-1

我通常做的是制作一个单独的包含文件,其中包含所有必要的依赖项以正确的顺序。所以我可能有:

#ifndef _PROJECT_H_
#define _PROJECT_H_
#include <someDependency.h>
#include "my_project_types.h"
#include "my_project_implementation_prototypes.h"
#endif

全部在 project.h 中。现在 project.h 可以包含在任何地方,没有顺序要求,但我仍然可以在不同的头文件中拥有我的依赖项、类型和 API 函数原型。

于 2009-11-26T18:11:02.290 回答
-2

只需在项目的一个通用头文件中包含所有外部头文件,例如global.h并将其包含在所有 c 文件中:

它看起来像这样:

#ifndef GLOBAL_GUARD
#define GLOBAL_GUARD

#include <glib.h>
/*...*/
typedef int  YOUR_INT_TYPE;
typedef char YOUR_CHAR_TYPE;
/*...*/
#endif

该文件使用 include 保护来避免多重包含、非法多重定义等。

于 2009-11-26T16:04:29.237 回答