工学1号馆

home

C语言头文件设计指南

Wu Yudong    December 06, 2016     C   632   

在C语言中,一个模块的内容包括结构类型(struct)声明、全局变量和函数。函数本身通常是定义在源文件中(一个“.c”文件)。除了主函数模块,每个源文件 (.c) 都有一个头文件 (.h) 与它相关联的,这个头文件提供了其他模块使用这个模块所需的声明。其他模块可以简单地通过 #include “X.h” 包含头文件来访问模块X的功能,其余的工作交给链接器完成。X.c 中的代码仅仅在第一次或者发生改变的时候才编译,其余时间链接器将链接X的代码到最后执行文件而不需要重新编译它

本文地址:http://wuyudong.com/2016/12/06/3138.html,转载请注明出处。

一个组织良好的C程序善于选择模块化,并正确构造的头文件,使它很容易理解和访问在一个模块的功能。他们也有助于确保该程序使用相同的声明和所有的程序组件的定义。这很重要,因为编译器和链接器需要执行一次定义规则(ODR)。

此外,精心设计的头文件,当其他组件的组件发生改变时,减少需要重新编译源文件。技巧是通过最大限度地减少模块的头文件所包含的头文件(#include)的数目,从而降低组件之间的耦合。在非常大的项目中,最大限度地减少耦合可以产生在编译时间方面的巨大差异,以及简化代码的组织和调试。

下面的规则总结了为了最大化的清晰和编译方便如何建立你的头文件和源文件

规则1. 每一个模块包含它的 .h 和 .c 文件应该对应一个明确的功能。从概念上讲,一个模块是一组声明和功能,可以独立于其他模块进行开发和维护,甚至可以在完全不同的项目重复使用。不要强迫将被单独使用或维护的代码放进同一个模块,也不要将一起使用和维护的代码分割开。标准库模块的 math.h 和 string.h 是清晰地区分不同模块方面很好的例子。

规则2.  总是使用头文件中的“include 守护”include guards)。最紧凑的形式是使用#ifndef。选择基于头文件名的符号,因为这些符号很容易被想起来,头文件名几乎在一个项目中是唯一的,按照所有字母都是大写的惯例。例如“Geometry_base.h”将这样开始:

#ifndef GEOMETRY_BASE_H
#define GEOMETRY_BASE_H
……
#endif

注意:守护符号不要以下划线开始,下划线开始的名字被保留作为 C 实现–预处理器、编译器和标准库的内部使用,打破这个规则会造成不必要的和非常令人费解的错误。下划线的完整的规则相当复杂,但如果你按照这个简单的形式,你会远离麻烦。

规则3. 所有需要使用模块的声明都必须出现在它的头文件中,而这个文件总是用来访问该模块的。因此#包含头文件提供了所有代码所需要的信息来使用模块进行正确的编译和链接。此外,如果模块 A 需要使用模块X的功能,它应该#include  “x.h”,绝不要使用硬编码声明来包含在模块 X 中的声明或函数。why?如果模块X改变了,但你忘记改变了声明在模块A中的硬编码,模块 A 可能很容易地产生微妙的运行时错误而失败,既不会被编译器或链接器检测到。总是记得一个模块通过其头文件确保只有单一的一套需要维护的声明,并有助于加强一次定义规则。

规则4. 头文件仅包含声明,为了模块化被.c文件包含。 在 .h 文件仅仅包含结构体声明、函数原型、全局变量和extern声明; 在 .c 文件中将函数定义和全局变量的定义和初始化。一个模块的 .c 文件必须包含 .h 文件; 编译器可以可以检测两者之间的差异,从而有助于保证一致性。

规则5. 设置程序范围内的全局变量,使用头文件中 extern 声明,并在 .c 文件中定义。 为了全局变量被整个程序访问,在 .h 文件放一个 extern 声明, 例如:

 extern int g_number_of_entities;

其他模块仅仅 #include .h 文件,模块中 的 .c 文件应该包含相同的 .h 文件, 在文件靠近开头的位置,应该出现一个定义声明 – 这个声明既定义又初始化全局变量,如:

int g_number_of_entities = 0;

当然,其他的非零值也可以用来作为初始化的值,静态/全局变量默认初始化为 0 ; 但明确初始化为 0 是习惯,因为它标志着这个声明作为定义的声明,以为着这是唯一的定义点。注意不同的 C 编译器和链接器将允许其他的方法来设置全局变量,但这是公认的C++来定义全局变量,并在 C 中也适用以及确保全局变量遵循一个定义规则。

规则6. 将模块的内部声明保存在头文件中。有时,一个模块不想被其他模块访问而使用严格的内部组件。如果您需要结构声明、全局变量或仅在 .c 文件中使用的函数,将它们的定义或声明放在C文件的顶部附近,并不要在 .h 文件中提及。在 .c 文件中声明静态的全局变量和函数来让它们在内部联系。

这种情况下,其他的模块不能访问这些模块内部的声明、全局变量或者函数。由静态声明导致的内部链接将使链接器帮助你执行计划的决策。

规则7. 每一个头文件 A.h 应该 #include 其他任何 A.h 需要来正确编译头文件,但不会更多。A.h 需要什么: If 如果结构体 X 作为结构体 A 中的一个成员变量,这样你必须在A.h 中 #include X.h,这样编译器就能知道成员 X 的大小。 不要包含仅仅 .c 文件中需要的头文件。 例如:<math.h> 通常是仅仅在函数定义的时候才被 .c 文件包含,而不是 .h 文件。

规则8. 如果一个不完整的结构体 X 声明将做什么,使用它而不是 #includ 它的头文件。如果一个结构体类型 X 仅仅作为一个结构体中的指针类型或函数中,并且头文件中的代码不打算访问 X 中的任何成员变量,那么你不应该#include X.h,而是让一个不完整的声明X(也叫“前进”的宣言)在x的第一次使用,这是一个在结构体中使用指向 X 的指针的例子:

struct X;
 /* 不完整的("forward")声明  */
struct Thing {
    int i;
    struct X *x_ptr;
};

结构体很乐意接受包含指向未完成的结构体类型指针的代码,主要因为指针不论指向什么,它都有相同的大小和特征。典型地,仅仅 .c 文件中的代码需要访问 X 中的成员或它的大小,于是 .c 文件将 #include “X.h”。这是一个封装模块并与其他模块解耦的强大技术。

规则9. A.c 文件应该首先#include A.h 文件,接着是代码所需要的其他头文件。通常 #include A.h 放在前面来避免隐藏任何其他从其他头文件的获取的。接着,如果 A 的实现代码使用 X,明确地在A.c 中 #include X,这样 A.c 不会依赖 X.h 意外被 #included 其他地方。

关于A.c 是否应该#include那些A.h 已经包含的头文件没有明确的共识

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)