瞻仰完大神改STM32中断向量表的代码,今天我们来看看“大神”写的枚举、结构、函数声明,还是一样的配方,一样的烧脑。
少废话,先放代码。第一段是枚举,第二段是函数,第三段是对一个数组的初始化。
//==============================================================================
#define UTIL_EVENT_Define(T,X)
#define UTIL_STATE_Define(T,X) UTIL_STATE_ENUM(T,X),
enum
{
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
};
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
//==============================================================================
//==============================================================================
#define UTIL_STATE_Define(T,X) UTIL_STATE_FUNC_Extern(T,X)
#define UTIL_EVENT_Define(T,X)
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
//==============================================================================
//==============================================================================
#define UTIL_STATE_Define(T,X) UTIL_STATE_FUNC_ITEM_IN_TAB(T,X),
#define UTIL_EVENT_Define(T,X)
const __pgm __state_func_t APP_PROC_STATE_M_NET_SETUP_TAB[]=
{
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
};
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
这个APP_PROC_STATE_M_NET_SETUP_define.h 头文件内容比较多,但是每一项都大同小异,我这里放一部分出来。
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,BEGIN)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,END)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,AT_E0)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,AT_CPIN)
不知道各位看官在第一次阅读这个代码的时候,有没有立马理解里面的门道。能写出这样代码的人,对编译器的理解,C语言的功力都是深厚的,不然也不能被叫做大神。接下来我就来好好理解理解里面的门道。
首先是对#include的理解。平时我们都知道要获取一个模块对外的函数声明,结构体枚举定义,变量声明等,要include那个模块的头文件。这只是会用,其实include的本质,是将指定头文件的内容替换掉#include这句,当然这个操作是编译器去执行的。
举个例子
//ModuleA.h
extern uint8_t Test;
uint8_t GetFun(void);
//ModuleB.c
#include "ModuleA.h"
....
....
....
我们在模块B的c文件中包含了模块A的头文件。编译器在编译的时候,首先会把模块B的c文件变成这样,然后才进行编译。
//ModuleB.c
extern uint8_t Test;
uint8_t GetFun(void);
...
...
...
宏定义的本质跟#include是一样的,编译器在编译之前,会把代码里的宏替换成定义的内容。
理解了这些,我们拿一开始那段代码里的枚举定义来看看
#define UTIL_EVENT_Define(T,X)
#define UTIL_STATE_Define(T,X) UTIL_STATE_ENUM(T,X),
enum
{
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
};
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
也就是说,编译器编译的时候会把APP_PROC_STATE_M_NET_SETUP_define.h文件里的内容“粘贴”到枚举定义里,我们试着还原一下“粘贴”后的效果
#define UTIL_EVENT_Define(T,X)
#define UTIL_STATE_Define(T,X) UTIL_STATE_ENUM(T,X),
enum
{
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,BEGIN)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,END)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,AT_E0)
UTIL_STATE_Define(APP_PROC_STATE_M_NET_SETUP_t,AT_CPIN)
};
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
这个时候留意一下,枚举定义之前,对UTIL_STATE_Define这个宏进行定义,实际情况应该是这样
enum
{
UTIL_STATE_ENUM(APP_PROC_STATE_M_NET_SETUP_t,BEGIN),
UTIL_STATE_ENUM(APP_PROC_STATE_M_NET_SETUP_t,END),
UTIL_STATE_ENUM(APP_PROC_STATE_M_NET_SETUP_t,AT_E0),
UTIL_STATE_ENUM(APP_PROC_STATE_M_NET_SETUP_t,AT_CPIN),
};
那这个UTIL_STATE_ENUM宏又是啥定义,我在另外一个头文件里找到了。
#define UTIL_STATE_ENUM(T,X) STATE_ENUM_##T##_##X
好了,那么枚举的终极展开就是这样,名字真的。。。哎,好长,好长。
enum
{
STATE_ENUM_APP_PROC_STATE_M_NET_SETUP_t_BEGIN,
STATE_ENUM_APP_PROC_STATE_M_NET_SETUP_t_END,
STATE_ENUM_APP_PROC_STATE_M_NET_SETUP_t_AT_E0,
STATE_ENUM_APP_PROC_STATE_M_NET_SETUP_t_AT_CPIN,
};
这份代码接下来还有个地方有意思,就是刚刚这个APP_PROC_STATE_M_NET_SETUP_define.h文件不仅仅被用在了枚举定义里,还被用在函数声明和数组初始化里。这是怎么做到的呢。
我们来看一下,函数声明部分。
#define UTIL_STATE_Define(T,X) UTIL_STATE_FUNC_Extern(T,X)
#define UTIL_EVENT_Define(T,X)
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
真鸡贼,在函数定义之前,把UTIL_STATE_Define这个宏重新定义了,这个宏就是头文件里的那个。UTIL_STATE_FUNC_Extern这个宏的定义如下。
#define UTIL_STATE_FUNC(T,X) STATE_FUNC_##T##_##X
#define UTIL_STATE_FUNC_INLINE(T,X) STATE_FUNC_##T##_##X##_
#define UTIL_STATE_FUNC_Extern(T,X) void UTIL_STATE_FUNC(T,X)(UTIL_EVENT_STATE_t *p_es);
又是各种拼接宏,看得头晕,我就不展开了,总之他通过这种方法,定义了一堆的函数,并且函数的名字靠编译器编译的时候去拼接。更鸡贼的是,他在函数定义之后,还#undef了之前定的宏,防止后面用错。
最后,我们来看一下数组的初始化。
#define UTIL_STATE_Define(T,X) UTIL_STATE_FUNC_ITEM_IN_TAB(T,X),
#define UTIL_EVENT_Define(T,X)
const __pgm __state_func_t APP_PROC_STATE_M_NET_SETUP_TAB[]=
{
#include "APP_PROC_STATE_M_NET_SETUP_define.h"
};
#undef UTIL_STATE_Define
#undef UTIL_EVENT_Define
还是一样的手法,我们只有看了UTIL_STATE_FUNC_ITEM_IN_TAB这个宏,才知道数组是怎么初始化的。
#define UTIL_STATE_FUNC_ITEM_IN_TAB(T,X) [UTIL_STATE_ENUM(T,X)]=UTIL_STATE_FUNC(T,X)
这里用到了之前枚举和函数定义的拼接宏。枚举,函数声明,数组初始化,合起来总的想法就是,定义一个函数指针数组,用枚举做索引,把函数名字写到数组的初始化。
确实,通过这一顿操作,少写了很多代码,这些代码都靠编译器自己完成了。但是可读性嘛,谁看谁知道。
看到这里,可能大家跟我有一样的疑问,这么花式地把枚举,函数给定义了,用的时候咋办咧。无他,还是靠拼接宏。比如要用到枚举,只能是这样
UTIL_STATE_ENUM(APP_PROC_STATE_M_NET_SETUP_t,BEGIN)
这样的代码,首先可读性十分差劲,滥用拼接宏,会频繁打断阅读的思路,最可恶的是函数的名字用拼接,在调试的时候往往无法直接跳转到定义,想要搜索也无从下手,效率低下。函数名字用拼接还有一个十分恶劣的影响,就是编译出错的时候,错误信息里函数的名字是编译器对宏进行完全展开后的名字,你看到函数的名字会非常的陌生和懵逼,肯定无法第一时间就想起来是哪个函数。这样的代码,也就适合拿来提升你对编译器和C语言的理解了。