C语言滥用头文件的危害(新手必看)
在 C 语言程序中,头文件很容易被滥用。例如在编写程序时,不管程序中是否会用到 printf()、scanf() 等标准 C 函数库定义的输入输出接口,都一律包含 #include <stdio.h> 这条预处理指令。这大概是因为“Hello, world!”程序的标准写法给人的印象实在太过深刻,让所有初学 C 语言的人以为所有的 C 程序都需要包含这个头文件。
通过回顾头文件的相关背景知识,我们已经知道,每包含一个头文件,就会导致预处理生成的源文件之大小增加一轮,而其中往往包含大量的原始程序并不需要的内容,这会带来两个方面的负面影响:
当然,以上负面影响在构建一个小型项目时通常可以忽略,但是当我们构建一个大型项目时,以上负面影响就会明显拖慢构建速度,甚至耗尽系统内存而导致构建失败。
滥用头文件,尤其是滥用系统头文件的另一个危害在于,这会损害程序的可移植性。曾有一名程序员整理了一份常用系统头文件的清单,他的习惯是在所有源文件的头部包含这些头文件:
有读者可能会问,有多少项目的源代码需要可移植性?以通用操作系统为例,目前已经形成了 Windows、macOS、Linux 三分天下的局面,不论我们使用 C 语言开发哪类应用,都有可能在这 3个平台上运行。而这 3 个平台提供的 C 语言系统接口,其交集只是 C90 标准。其中,macOS 和 Linux 平台上的 C 语言系统接口重叠部分相对较多,毕竟都属于类 UNIX 操作系统;但 Windows 平台上的 C 语言系统接口和 ISO C99、POSIX 等均有较大的差异。
除了通用操作系统,还有几十种小型实时操作系统或物联网操作系统。这些操作系统提供的基础 C 接口往往残缺不全,更不用说全面的 POSIX 兼容了。
举个例子,小型实时操作系统或物联网操作系统一般不支持多进程,其多任务能力通常可视作通用操作系统的单个进程中的线程。故而,我们也许可以在这类操作系统中使用 POSIX 定义的线程、互斥量和条件变量接口,如 pthread_create() 和 pthread_mutex_init(),但无法使用 POSIX 定义的进程相关接口,如 fork()、wait() 等,也无法使用进程间通信机制,如共享内存。
另外,这类操作系统中通常不存在字符终端,也便没有了标准 C 函数库定义的标准输入输出,于是我们常用的 printf()、scanf() 等接口都不可用。
因此,在使用头文件时,不论是系统头文件还是我们为应用程序定义的头文件,都应该避免滥用,仅包含程序必需的那些头文件。
通过回顾头文件的相关背景知识,我们已经知道,每包含一个头文件,就会导致预处理生成的源文件之大小增加一轮,而其中往往包含大量的原始程序并不需要的内容,这会带来两个方面的负面影响:
- 编译器处理冗余信息需要更多的处理器时间;
- 编译器需要更多的系统内存来保存名称信息,如枚举量名称、数据类型或结构体名称、函数原型等。
当然,以上负面影响在构建一个小型项目时通常可以忽略,但是当我们构建一个大型项目时,以上负面影响就会明显拖慢构建速度,甚至耗尽系统内存而导致构建失败。
滥用头文件,尤其是滥用系统头文件的另一个危害在于,这会损害程序的可移植性。曾有一名程序员整理了一份常用系统头文件的清单,他的习惯是在所有源文件的头部包含这些头文件:
#include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> ...假设一个源文件只用于实现一个平台无关的哈希表,而无须使用任何操作系统特有的接口,那么当我们将这个源文件放到 Windows 等平台上编译时,就不得不修改该源文件,以删除那些和特定操作系统相关的头文件。显然,这种用法会给 C 程序的可移植性带来很多麻烦。
有读者可能会问,有多少项目的源代码需要可移植性?以通用操作系统为例,目前已经形成了 Windows、macOS、Linux 三分天下的局面,不论我们使用 C 语言开发哪类应用,都有可能在这 3个平台上运行。而这 3 个平台提供的 C 语言系统接口,其交集只是 C90 标准。其中,macOS 和 Linux 平台上的 C 语言系统接口重叠部分相对较多,毕竟都属于类 UNIX 操作系统;但 Windows 平台上的 C 语言系统接口和 ISO C99、POSIX 等均有较大的差异。
除了通用操作系统,还有几十种小型实时操作系统或物联网操作系统。这些操作系统提供的基础 C 接口往往残缺不全,更不用说全面的 POSIX 兼容了。
举个例子,小型实时操作系统或物联网操作系统一般不支持多进程,其多任务能力通常可视作通用操作系统的单个进程中的线程。故而,我们也许可以在这类操作系统中使用 POSIX 定义的线程、互斥量和条件变量接口,如 pthread_create() 和 pthread_mutex_init(),但无法使用 POSIX 定义的进程相关接口,如 fork()、wait() 等,也无法使用进程间通信机制,如共享内存。
另外,这类操作系统中通常不存在字符终端,也便没有了标准 C 函数库定义的标准输入输出,于是我们常用的 printf()、scanf() 等接口都不可用。
因此,在使用头文件时,不论是系统头文件还是我们为应用程序定义的头文件,都应该避免滥用,仅包含程序必需的那些头文件。