头文件管理
目录
前言
头文件包含方式
头文件包含方案一
目录结构
文件内容
Makefile编写
头文件包含方案二
目录结构
最佳实践
规范写法总结
Linux头文件包含方案思考
前言
由于工程项目中所涉及的头文件与源文件数量庞大,可以看见各种各样的头文件的包含方式, 当引入了Makefile自动化编译工程时,笔者实际操作时发现头文件的包含远远不止表面现象那么简单,背后的原因需要深究才能帮助笔者建立理性的分析思路,所以需要探讨常见场景下的头文件包含方案;
头文件包含方式
当编译器在处理
#include "file.h"
时,会将源文件的绝对路径作为基准,源文件所在目录(即.c/.cc文件所在目录),由于目录结构本质为一种命名空间,源文件目录作为 "本地命名空间",优先于全局系统目录,所以最先查找源文件所在目录;
-I
选项在预处理阶段直接扩展头文件搜索路径,为了满足开发者对自定义头文件的优先引用需求,若编译器先搜索系统目录,会导致用户自定义头文件被系统同名文件覆盖,开发者通过-I
显式指定本地头文件路径,确保自定义内容优先被引用;系统默认头文件目录是编译器在编译过程中自动搜索标准库头文件的位置,我们采用如下指令来查看 GCC 编译器的详细编译过程及默认头文件搜索路径
# 将空设备文件/dev/null作为标准输入传递给编译器并输出预处理阶段详细的信息gcc -v -E - < /dev/null
采用
-I
选项设置头文件搜索路径为inc,通过cpp -v
命令查看预处理阶段的搜索路径是否被成功设置;
cpp -v -I ./inc - < /dev/null
头文件包含方案一
目录结构
文件内容
Makefile编写
# 工具链配置
CC=gcc
# 头文件位置
INC_DIRS=./inc
# 编译器选项配置
CFLAGS=-Wall -O2 -std=c99 -I$(INC_DIRS)
# 目标名称
TARGET=main
# 源文件位置
SRC_DIRS=./src# 源文件列表
SRCS= $(SRC_DIRS)/file1.c $(SRC_DIRS)/file2.c $(SRC_DIRS)/file3.c $(SRC_DIRS)/main.c# 编译
$(TARGET):$(SRCS:.c=.o)$(CC) $^ -o $@
%.o:%.c$(CC) $(CFLAGS) $< -c -o $@.PHONY:clean
clean:rm -rf $(TARGET) $(SRCS:.c=.o)
Makefile中 编译器选项配置 通过 -
I
选项 告知编译器去 ./inc
目录 搜索头文件,通过编译运行进而判断此种头文件包含方式是否成功;
头文件包含方案二
目录结构
将方案一目录结构修改为方案二,不改变Makefile中 -
I
选项,makefile不发生任何变化,首先file.h文件位置的变化影响到main.c文件与file1.c文件,此时main.c文件与file1.c文件应该如何包含file1.h(file2.c file3.c file1.h file2.h file3.h内容同方案一)
通过编译运行检测此种包含头文件的方案是否可以保证编译器找到头文件的位置;
结论:只有当 头文件位于
-I
指定目录的子目录中 时,才需要在#include
中写子目录路径;
最佳实践
-I
管理路径,#include
只关心 "文件名 + 层级"
-I
选项:用于告知编译器 "头文件的根搜索目录是什么"(如inc/
目录);#include
语句:用于告知编译器 "头文件在根搜索目录下的相对路径是什么"(如file1.h
或utils/file1.h
);
规范写法总结
Linux头文件包含方案思考
当采用Linux进行系统编程或者网络编程时,通常书写头文件的方式如下,原因又是什么呢
#include <sys/types.h> // 基本系统数据类型
#include <sys/stat.h> // 文件状态
#include <sys/socket.h> // 套接字接口
Unix/Linux 系统设计遵循 "目录即命名空间" 原则,通过文件系统路径实现功能隔离,编译器会自动递归搜索系统默认头文件路径下的所有子目录,Linux系统可以轻松获得头文件的路径,但是为啥我们需要采用 #include
<目录名/文件名>
头文件格式呢?
假如直接采用 #include <types.h> , 编译器无法确定是:/usr/include/sys/types.h(POSIX 标准)/usr/include/linux/types.h(Linux 内核)
Linux底层系统调用接口采用 #include
<目录名/文件名>格式
使得用户可以
显式指定头文件的功能模块和来源,避免命名冲突;
实践方案
Linux系统头文件: 直接用 <目录名/文件名>, 无需 -I 指定头文件搜索路径;第三方库(BOOST): 用 -I指定库的根目录, 头文件用 <库名/文件名>