今天看了一遍博客《Reduce C-language coding errors with X macros》,感觉文章写得非常的好,也对自己写代码有了一定的帮助,所以就将该文章用我自己的语言,写出来和大家分享分享吧。
在嵌入式实时操作系统中,经常将系统分成很多层次,很多个模块,每一个模块都有自己的初始化过程,这时候我们一般采用的形式如下所示:
点击(此处)折叠或打开
- typedef void(*p_func_t)(void);
- enum
- {
- STATE_0,
- STATE_1,
- STATE_2,
- ...
- STATE_M,
- NUM_STATES
- };
- p_func_t inital_table[NUM_STATES]
- {
- func_0,
- func_1,
- func_2,
- ...
- func_M,
- };
这种实现方法是比较常见的实现方式,但是这种方法的缺点是所有的初始化过程是按照一定的顺序的,而且不能随机的初始化,因此如果在编码的过程中将状态号与初始化函数对应错误,将出现比较难以发现的错误,这种错误经常出现,当然有些编译器以及支持随机的初始化过程,但是并不具有通用性,而且这种实现方式代码比较多,能不能采用宏定义的方式简化代码量呢?当然是可以的,采用类似于函数的宏定义就可以实现,具体的实现如下:
点击(此处)折叠或打开
- typedef void(*p_func_t)(void);
- #define STATE_TABLE\
- ENTRY(STATE_0,func_0)\
- ENTRY(STATE_1,func_1)\
- ENTRY(STATE_2,func_2)\
- ENTRY(STATE_3,func_3)\
- ENTRY(STATE_4,func_4)
- enum{
- #define ENTRY(a,b) a,
- STATE_TABLE
- #undef ENTRY
- NUM_STATES
- };
- p_func_t inital_table[NUM_STATES] =
- {
- #define ENTRY(a,b) b,
- STATE_TABLE
- #undef ENTRY
- };
上面这种实现方式的优点是运用了宏定义简少代码量。我做一个简要的分析,首先采用宏定义定义了一组ENYRT,其中包含两个参数,分别是状态号STATE_N,和状态对应的初始化函数,这种实现方式能够避免上面所谓的状态号与函数对应错误的问题,因为在宏定义的过程中一般都会认真的确定各种接口,对应好了只需要定义相关的函数就可以啦。在enum中采用了#define和#undef来限定这一组宏定义的作用范围,在个作用域中,ENTRY(a,b)是表示“a,”,需要注意不能忽略a后的','因为这就是在enum中定义变量后要添加的符号,我想大家应该知道enum{a,b,c,d}每一个成员后面都包含","的特性的。也就是说在这作用域中,ENTRY(a,b)被替换为"a,",那么这时候STATE_TABLE就被替换为STATE_0,STATE_1等,然后和NUM_STATES就组成了第一个例程中的enum结构。而在p_func_t jumptable[NUM_STATES]仍然采用了了STATE_TABLE,由于采用了#define和#undef限定了宏的作用范围,这时的ENTRY(a,b)将被替代为“b,”,也就是func_0,func_1等,这样也就完成了函数指针数组的初始化过程,这样的初始化能够减少状态号与初始化函数对应出错的情况。
这样的实现也可以认为是宏定义的巧妙运用,但是这种方法还是存在一些问题,因为采用#define 和#undef这种方法很可能导致错误的产生,因为很有可能不能很好的把握这个限定作用域的使用方法,这时候可以采用一种新的类似函数的实现方法,可以让STATE_TABLE带一个参数,也就是采用类似命令的形式定义相关的内容:
点击(此处)折叠或打开
- typedef void(*p_func_t)(void);
- /*以下产生几个常用的命令*/
- /*enum产生*/
- #define EXPAND_AS_ENUM(a,b) a,
- /*初始化表产生*/
- #define EXPAND_AS_INITTABLE(a,b) b,
- /*声明各个函数*/
- #define EXPAND_AS_FUNCDEC(a,b) void b(void);
- /*将STATE_TABLE的参数就是具体的命令*/
- #define STATE_TABLE(ENTRY)\
- ENTRY(STATE_0,func_0)\
- ENTRY(STATE_1,func_1)\
- ENTRY(STATE_2,func_2)\
- ENTRY(STATE_3,func_3)\
- ENTRY(STATE_4,func_4)
- /*定义enum*/
- enum{
- STATE_TABLE(EXPAND_AS_ENUM)
- NUM_STATES
- };
- /*声明各个函数*/
- STATE_TABLE(EXPAND_AS_FUNCDEC)
- /*初始化表*/
- p_func_t inital_table[NUM_STATES] =
- {
- STATE_TABLE(EXPAND_AS_INITTABLE)
- };
以上实现方法能够较好的避免#define和#undef的限定作用域问题,这实际上采用ENTRY作为参数传递给STATE_TABLE,然后ENTRY可用来实现不同的指令,这些指令的定义也是一系列的宏定义,这种实现架构能够比较好的避免缺少声明等问题。同时也较少了错误的产生可能。
这种实现模型只是简化的版本,STATE_TABLE(ENRTY)中的ENTRY可以定义多个参数如下所示:
点击(此处)折叠或打开
- /*COMMANDS*/
- #define COMD1(a0,a1,a2,...,am) /*具体实现*/
- #define COMD2(a0,a1,a2,...,am) /*具体实现*/
- ...
- #define TABLE(ENTRY)\
- ENTRY(a0,a1,a2,...,am)\
- ENTRY(a0,a1,a2,...,am)\
- ...
- ENTRY(a0,a1,a2,...,am)\
比如将上面的初始化实现修改为下面的形式,也就是多个参数的形式,实现如下:
点击(此处)折叠或打开
- typedef void(*p_func_t)(void);
- #define EXPAND_AS_ENUM(a,b,c) a,
- #define EXPAND_AS_JUMPTABLE(a,b,c) b,
- #define EXPAND_AS_FUNCDEC(a,b,c) void c(void);
- #define STATE_TABLE(ENTRY)\
- ENTRY(STATE_0,func_0,func_0)\
- ENTRY(STATE_1,func_1,func_1)\
- ENTRY(STATE_2,func_2,func_2)\
- ENTRY(STATE_3,func_3,func_3)\
- ENTRY(STATE_4,func_4,func_4)
- enum{
- STATE_TABLE(EXPAND_AS_ENUM)
- NUM_STATES
- };
- STATE_TABLE(EXPAND_AS_FUNCDEC)
- p_func_t init_table[NUM_STATES] =
- {
- STATE_TABLE(EXPAND_AS_INITTABLE)
- };
上面的实现并不是非常的恰当,因为第二个、第三个参数实质上是一致的,没有必要定义为三个参数。本文只是说明三个参数的实现情况。其他多个参数的实现情况类似。为了说明这种模型的可行性,我写了简单的测试代码,由于各个模块的初始化代码需要程序员手动的实现,因此可以定义在其他的位置,同时在宏定义中也已经实现了各个函数的声明问题,因为不会出现未定义的问题。具体的实现如下所示:
点击(此处)折叠或打开
- #include<stdio.h>
- typedef void(*p_func_t)(void);
- #define EXPAND_AS_ENUM(a,b) a,
- #define EXPAND_AS_INITTABLE(a,b) b,
- #define EXPAND_AS_FUNCDEC(a,b) void b(void);
- #define STATE_TABLE(ENTRY)\
- ENTRY(STATE_0,func_0)\
- ENTRY(STATE_1,func_1)\
- ENTRY(STATE_2,func_2)\
- ENTRY(STATE_3,func_3)\
- ENTRY(STATE_4,func_4)
- enum{
- STATE_TABLE(EXPAND_AS_ENUM)
- NUM_STATES
- };
- STATE_TABLE(EXPAND_AS_FUNCDEC)
- p_func_t init_table[NUM_STATES] =
- {
- STATE_TABLE(EXPAND_AS_JUMPTABLE)
- };
- /*测试代码*/
- int main()
- {
- int i = 0;
- for(i = 0; i < NUM_STATES; ++ i)
- (jumptable[i])();
- return 0;
- }
- /*各个模块的初始化函数实现*/
- void func_0(void)
- {
- printf("In func_0\n");
- }
- void func_1(void)
- {
- printf("In func_1\n");
- }
- void func_2(void)
- {
- printf("In func_2\n");
- }
- void func_3(void)
- {
- printf("In func_3\n");
- }
- void func_4(void)
- {
- printf("In func_4\n");
- }
关于多变量的情况,在Linux内核源码中的物理内存与虚拟内存之间可以采用这种方式实现,在很多情况下都知道寄存器的物理内存,一般一组相关的寄存器的映射方式都是相同的,采用这种宏定义的实现方式就能较好完成定义问题,当然只是可行的方法而已。
点击(此处)折叠或打开
- #define ADDRESS_OFFSET (0x8000)
- #define PHYS_ADDRESS(a,b,c) a=c;
- #define VIRU_ADDRESS(a,b,c) a = b + c;
- #define REGISTER_MAP(ENTRY)
- ENTRY(reg0,ADDRESS_OFFSET,0x10)\
- ENTRY(reg1,ADDRESS_OFFSET,0x14)\
- ENTRY(reg2,ADDRESS_OFFSET,0x18)\
- ...
- ENTRY(regm,ADDRESS_OFFSET,0x2c)
- /*物理地址*/
- REGISTER_MAP(PHYS_ADDRESS)
- /*虚拟地址*/
- REGISTER_MAP(VIRU_ADDRESS)
宏定义的这种实现方式在一些嵌入式系统中是非常有效的,掌握这种实现方法能够较好的管理各个模块、各种状态下对应的处理函数。这是一种经典的用法。这实际上给出可一种解决问题的模型架构,掌握好这种方式能够较好的实现模块的管理问题。这种实现方法不仅代码量少,而且能够避免很多错误的产生,能够快速的进行修改,但是难点在于代码对于初学者有一定的难度,而且宏定义实现的函数还能够采用其他的方法实现,只是宏定义能够较好的简化代码,使得代码优美且易维护。