如果在标头(* .h)中使用了此文件中定义的类型,是否有必要#include
一些文件?
例如,如果我使用GLib并希望在标头中定义的结构中使用gchar
基本类型,那么知道我的* .c文件中已经包含#include <glib.h>
了吗?
如果是,我还必须将其放在#ifndef
和#define
之间还是#define
之后?
最佳答案
NASA的戈达德太空飞行中心(GSFC)规定C头为标头,必须有可能在源文件中包含头作为唯一的头,然后将使用该头提供的功能来编译该代码。
这意味着标头必须是独立的,幂等的并且是最小的:
自包含的-通过在必要时包含相关标头来定义所有必需的类型。
幂等—即使多次被包含,编译也不会中断。
最小—它没有定义使用头文件访问头文件定义的功能所不需要的代码。
此规则的好处是,如果有人需要使用标头,则不必费力确定还必须包含哪些其他标头-他们知道标头提供了所有必要的东西。
可能的不利之处是某些标头可能会多次包含在内;这就是为什么多重包含标头保护至关重要的原因(以及为什么编译器尽量避免重新包含标头的原因)。
实作
此规则意味着,如果标题使用一种类型,例如“ FILE *
”或“ size_t
”,则它必须确保应包含适当的其他标题(例如,<stdio.h>
或<stddef.h>
)。一个经常被遗忘的推论是,标头不应该包括包装用户使用该包装所不需要的任何其他标头。换句话说,标题应该最小。
此外,GSFC规则提供了一种简单的技术来确保发生这种情况:
在定义功能的源文件中,标头必须是列出的第一个标头。
因此,假设我们有一个魔术排序。
魔术排序
#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 */
魔术排序
#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
语句。所有#include
在主体文件中完成。然后,单元头文件必须包含用于检查的#ifdef
语句所需的标题以正确的顺序包含在内。
另一种设计的优点是主体文件中的
#include
列表恰好是生成文件中需要的依赖项列表,并且此列表由编译器检查。符合标准
设计中,必须使用工具来生成依赖项列表。但是,所有分支
推荐的开发环境提供了这样的工具。
替代设计的主要缺点是,如果单元所需的标题列表发生更改,则每个文件
必须编辑使用该单元的代码以更新
#include
语句列表。另外,所需的标题列表编译器库单元在不同的目标上可能有所不同。
替代设计的另一个缺点是编译器库头文件和其他第三方
文件,必须进行修改以添加必需的
#ifdef
语句。另一种不同的惯例是在所有项目标头文件之前,先包含所有系统标头文件,
正文文件。该标准不遵循这种做法,因为某些项目头文件可能
取决于系统头文件,这是因为它们使用了系统头文件中的定义,或者
因为他们想覆盖系统定义。此类项目头文件应包含
#include
系统标题的声明;如果主体首先包含它们,则编译器不会对此进行检查。
GSFC标准可通过Internet存档获得2012-12-10
Information由Eric S. Bullington提供:
可以通过Internet档案访问和下载参考的NASA C编码标准:
http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?TypeAsset=Standard
排序
问题还问:
如果是,我还必须将其(
#include
行)放在#ifndef
和#define
之间或#define
之后。答案显示了正确的机制—嵌套的include等应位于
#define
之后(并且#define
应为标头中的第二个非注释行)—但是它不能解释为什么这是正确的。考虑如果将
#include
放在#ifndef
和#define
之间会发生什么。假设另一个标头本身包括各种标头,甚至可能间接包含#include "magicsort.h"
。如果magicsort.h
的第二个包含出现在#define MAGICSORT_H_INCLUDED
之前,则在定义其定义的类型之前将第二次包含该标头。因此,在C89和C99中,任何typedef
类型名称都将被错误地重新定义(C2011允许将它们重新定义为相同类型),并且您将获得多次处理文件的开销,从而破坏了标头保护的目的。首先。这也是#define
是第二行而不是仅在#endif
之前写入的原因。给出的公式是可靠的:#ifndef HEADERGUARDMACRO
#define HEADERGUARDMACRO
...original content of header — other #include lines, etc...
#endif /* HEADERGUARDMACRO */
关于c - 我应该在标题中使用#include吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59622545/