本文从汽车软件开发的角度出发,研究C语言中头文件的作用。各行各业都有自己一套规范或者约定俗成的规则,所以本文不一定适用于所有C语言开发的情形。
1 头文件的作用
在C语言编程中,头文件有着十分重要的作用,最核心的作用就是对外声明函数或全局变量。初次之外,还有类型定义、宏定义等功能。
一个好的头文件设计,可以清晰地解释系统架构以及调用关系。接下来博主会从工作经验出发,研究C语言的头文件中包含哪些信息。
2 头文件的内容
2.1 #define保护
拿到一个头文件,例如vc_vehicle_speed_control.h,在顶部会看到类似如下的代码:
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
//一些外部声明
#endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
从字面上理解,就是如果没有定义宏VC_VEHICLE_SPEED_CONTROL,那么就定义它,然后处理下面的外部声明等头文件核心内容。
这样做的目的是为了防止头文件被重复包含。在一个文件中第二次包含这个头文件时,由于已经定义过宏VC_VEHICLE_SPEED_CONTROL,在预处理阶段就不会把这个头文件中的内容展开来,就达到了防止重复包含的效果。
一般来说,这个保护用的宏地名称和头文件名称相同,但是全部改成大写。
2.2 包含其他头文件
头文件中可以包含其他头文件,这样编译器在预处理阶段就会一层层地展开内容。
例如有三个模块A,B,C实现不同功能,它们分别有三个头文件A.h,B.h,C.h。通过一个总的头文件common.h包含这三个头文件,其他模块D就可以直接包含common.h来调用这三个模块中的函数。
//common.h
#ifndef COMMON_H
#define COMMON_H
#include "A.h"
#include "B.h"
#include "C.h"
#endif /* COMMON_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
另外,也有一种做法是把一些公共的宏定义或类型定义写在一个头文件中,其他头文件再去包含这个都用得到的头文件,例如包含类型定义的头文件。
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
#include "common_types.h"
//一些外部声明
#endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
系统头文件和用户用户头文件的符号有所区别:
#include
#include "user.h"
- 1
- 2
对于用户头文件,如果不在同一个路径下,最好用相对路径来表示:
#include "../module1/user.h"
- 1
2.3 变量与函数的声明
变量与函数的声明是头文件中最重要的内容,其他文件包含头文件的目的就是为了调用这些变量和函数。例如在vc_vehicle_speed_control.h声明了对外的变量与函数接口。
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
#include "common_types.h"
extern const float32 vehicle_width;
void vc_vehicle_speed_control_init(void);
void vc_vehicle_speed_control_cycle(void);
#endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
从上面的代码中很容易理解到,头文件外部声明了一个常量vehicle_width,数据类型是32位浮点型。声明了两个函数,一个用于初始化,一个用于周期调度,函数传参和返回值都是void类型,一目了然地知晓了对应的c文件vc_vehicle_speed_control.c中定义了哪些变量,哪些函数。在项目中,如果想要快速地熟悉软件架构和功能接口,只需要看一看头文件中的定义,以及头文件的调用关系即可。
2.4 extern “C”
extern “C” 常用于C和C++混合编程中。例如,嵌入式软件中用的C代码编程,实现某种汽车零部件功能。也可以把这段C代码拿到VisualStudio或者其他C++工程中进行编译,从而脱离嵌入式环境中仿真。
由于C++编译和C的编译规则有所不同,就需要告诉编译器哪些函数是C函数,这就要用到extern “C” 。用法如下:
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
#include "common_types.h"
#ifdef __cplusplus
extern "C" {
#endif
extern const float32 vehicle_width;
void vc_vehicle_speed_control_init(void);
void vc_vehicle_speed_control_cycle(void);
#ifdef __cplusplus
}
#endif
#
endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
上面的意思是,如果定义了__cplusplus这个宏,就用extern “C” {}把函数的外部声明给包裹起来,这样编译器就知道这些是C函数。
注意一点,include头文件不要写到extern “C” {}中去。
3 头文件使用的规范
博主根据自己工作经验总结一些头文件的规范。不同行业、产品、团队都可能有自己的规范,这里仅供参考。
3.1 C文件和头文件
C文件中如果有被外部引用的函数,应该有一个同名的头文件,用于外部声明一些函数。这个头文件中只有外部需要引用的接口,而不包括C文件私有的函数、宏定义、枚举量等。这些私有的定义应该放在其他的头文件中,例如如下:
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
void vc_vehicle_speed_control_init(void);
void vc_vehicle_speed_control_cycle(void);
#endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
//vc_vehicle_speed_control_private.h
#ifndef VC_VEHICLE_SPEED_CONTROL_PRIVATE_H
#define VC_VEHICLE_SPEED_CONTROL_PRIVATE_H
typedef struct VC_VEHICLE_SPEED_TAG
{
float32 display_vehicle_speed;
float32 actual_vehicle_speed;
}VC_VEHICLE_SPEED;
typedef enum VC_GEAR_POSITION_TAG
{
GEAR_P = 0;
GEAR_N;
GEAR_D;
GEAR_R;
}VC_GEAR_POSITION;
#endif /* VC_VEHICLE_SPEED_CONTROL_PRIVATE_H*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
其中,车速和挡位是C文件私有的类型,就放在_private.h中,只被自己的c文件调用。对外声明接口的头文件,可以被外部调用。
另外指出一点,调用其他文件的函数时,只能通过包含头文件(因为头文件中有外部声明函数),不应该在自己的文件中直接extern声明函数。
3.2 调用外部函数和变量
调用外部函数和变量的时候,必须通过包含其头文件的方式调用,而不能在自身中直接用extern语句进行外部声明(即使语法运行这么做)。例如下面的周期调度文件调度一个任务函数。
//OS.c
extern void vc_vehicle_speed_control_cycle(void);
void Task_20ms(void)
{
vc_vehicle_speed_control_cycle();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在OS.c文件中想要把vc_vehicle_speed_control_cycle();函数加入20ms的周期调度中,但是没有包含头文件,直接extern声明。这样是不合理的,因为没有表现出各个文件之间的依赖关系。
正确做法如下:
//OS.c
#include "vc_vehicle_speed_control.h"
void Task_20ms(void)
{
vc_vehicle_speed_control_cycle();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
//vc_vehicle_speed_control.h
#ifndef VC_VEHICLE_SPEED_CONTROL_H
#define VC_VEHICLE_SPEED_CONTROL_H
void vc_vehicle_speed_control_init(void);
void vc_vehicle_speed_control_cycle(void);
#endif /* VC_VEHICLE_SPEED_CONTROL_H */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4 总结
C语言头文件的知识不止于此,后续博客会在工作和实践过程中积累并更新。
评论记录:
回复评论: