c - 我应该在标题中使用#include吗?

标签 c c-preprocessor file-organization

如果在标头(* .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

InformationEric 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/

相关文章:

c - 你如何在 C 中定义多行宏?

java - Eclipse 将包组织到文件夹层次结构中

c - OpenMP 点积和指针

C:换行十六进制值

C - 使用二维数组作为参数

c - 我如何在 Bloodshed/Dev C++ 编译器中使用二合字母和三合字母

c - time_t now=time(NULL); 之间有区别吗?现在是time_t;是时候了);?

c - C 中的 token 粘贴不清楚

svn - 你的 SVN 有多精细 "projects": one big project containing several releated apps or one "project' per app

python - 将 Python Flask 应用程序拆分为多个文件