文章目录
-
- [0 前言](#0 前言)
- [1 先说结论](#1 先说结论)
-
- [1. 1为什么有 `#ifndef` 还是会变慢?](#ifndef` 还是会变慢?)
- [1.2 `#include` 放在 `.c` 还是 `.h` 更好?](#include
放在.c还是.h` 更好?)
- [2 总结](#2 总结)
0 前言
在写C语言时,相信很多人都会有一个疑问,那就是include语句到底是放在C文件还是h文件,首先肯定是得保证编译通过,且没有警告,但这篇文章试图从编译速度的角度来考虑包含语句如何放置。
以下内容来自AI生成,并经过验证。
1 先说结论
即使每个头文件都有 #ifndef 防重复包含,过多包含依然会拖慢编译速度;而对于 .c 和 .h 文件,#include 应该优先放在 .c 文件中,只在必要时才放在 .h 里。
1. 1为什么有 #ifndef 还是会变慢?
#ifndef(Include Guard)只能防止同一个编译单元内 对同一头文件的多次、重复 展开。但"编译慢"的问题往往不源于重复包含,而是源于预处理阶段的开销 和不必要的依赖传递。
-
预处理开销 :每遇到一个
#include,预处理器都必须去磁盘上找到并打开那个头文件,读取其内容,检查#ifndef宏是否已定义,然后决定是全部展开还是跳过。即使跳过,文件打开、读取、宏检查的时间也已经消耗了。如果工程很大,头文件很多,这些微小开销累积起来就相当可观。比如一个.c文件间接包含了上百个头文件,即使每个都被#ifndef保护,预处理器依然要打开并处理这上百个文件。 -
依赖传递污染 :假设
a.h包含了b.h,而b.h包含了c.h。那么任何包含a.h的.c文件,都会被强制拉入b.h和c.h的内容(即使它根本用不到)。这会导致:- 编译器需要解析更多代码,增加内存和编译时间。
- 如果修改了
c.h,所有间接包含它的.c文件都必须重新编译(即使它们只用a.h中的一个不依赖c.h的宏)。在大项目中这会引发"级联重编译",极大拖慢速度。
1.2 #include 放在 .c 还是 .h 更好?
核心原则:最小依赖原则。能放在 .c 里的,绝不放在 .h 里。
-
放在
.c文件的好处:- 隐藏实现细节 :
.c文件需要的头文件(如特定库、内部数据结构)不必暴露给外部。外部只要包含.h就能使用你的模块,不会额外引入一堆没用的头文件。 - 减少编译依赖 :如果
foo.c需要bar.h,但这个头文件只在.c里包含,那么修改bar.h时,只有foo.c需要重编译。其他包含foo.h的.c文件完全不受影响。 - 加快整体编译 :每个
.c文件只包含自己真正需要的头文件,避免了通过.h传递带来的大量无用代码和解析开销。
- 隐藏实现细节 :
-
放在
.h文件的若干场景(仅限必要时):- 头文件自身的声明依赖另一个头文件的类型。例如
my.h里定义了一个函数,参数是struct Other,那么my.h必须包含other.h(或前向声明,若能用指针则可以前向声明避免包含)。 - 头文件定义了内联函数或宏,且它们使用了其他头文件的定义。
- 头文件需要某个常量或枚举,且该常量来源于另一个头文件。
- 头文件自身的声明依赖另一个头文件的类型。例如
典型的最佳实践:
c
// foo.h ------ 头文件尽量精简
#pragma once // 或 #ifndef FOO_H ...
#include "global_types.h" // 仅当 foo.h 自身必须用到其定义(如参数类型)
// 不要包含 global_helpers.h 等非必需的头文件
typedef struct foo foo_t;
void foo_init(foo_t* f);
void foo_do_something(foo_t* f);
c
// foo.c ------ 源文件包含所有具体依赖
#include "foo.h"
#include "global_helpers.h" // 实现中用到的,不暴露给外部
#include <stdio.h> // 具体实现需要的库
#include "private_data.h" // 内部数据结构
// 实现代码...
一个重要的加速技巧 :在 .h 中,对于只是指针或引用 的类型,尽量使用前向声明来避免包含完整的头文件。
c
// bar.h
struct big_struct; // 前向声明,不包含 big_struct.h
void process(struct big_struct* data); // 只需要指针,不需要完整定义
2 总结
| 问题 | 结论 |
|---|---|
有 #ifndef 还会变慢吗? |
会。因为预处理文件打开/读取开销、以及不必要的依赖传递仍然存在。 |
#include 放 .c 还是 .h? |
优先放 .c 。.h 只放自身确实需要的其他头文件。 |
| 如何进一步提速? | 使用前向声明、预编译头(PCH)、或模块化(C20 Modules,如果允许)。 |