C/C++中宏定义的经典运用

在C语言中宏定义是比较有用的技巧,在Linux源码中经常使用一些宏定义,比如宏container_of()等都是经典的宏定义表示方式。在C++不再主张使用宏定义,但是宏定义实际上却是是一个非常有用的手段。实质上宏定义能够搞定的实现采用其它的实现也是可以的,宏定义的作用是简单的替代作用,掌握这个是理解的关键,以前在没有代码阅读量的时候总是以为宏定义就是简单的定义一些常量什么的,实质上不然,宏定义完全可以写成函数的形式,但是宏定义和函数有一定的差别,函数的调用一般采用栈的方式实现,这时候存在局部变量,形参、实参等问题,如果不理解C语言的本质,很多时候非常容易搞错,但是宏定义则不然,宏定义没有调用的过程,宏的实现仅仅是一个替换过程,不用压栈出栈,宏实现的类似函数修改的就是当前区域中的变量,不是局部变量。这也是一些较深层次的问题,在刚学习C语言的时候很多人只要看见类似于函数的形式都认为是函数,实质上不一定,很有可能就是采用宏定义实现的类函数,这时候就可能导致所谓的形参实参问题发生较大的变化。关于宏的问题在面试笔试的过程中、写代码的过程中都是非常有用的部分,我就不再做介绍。

今天看了一遍博客《Reduce C-language coding errors with X macros》,感觉文章写得非常的好,也对自己写代码有了一定的帮助,所以就将该文章用我自己的语言,写出来和大家分享分享吧。

本文引用地址:http://www.eepw.com.cn/article/201612/324488.htm

在嵌入式实时操作系统中,经常将系统分成很多层次,很多个模块,每一个模块都有自己的初始化过程,这时候我们一般采用的形式如下所示:

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可用来实现不同的指令,这些指令的定义也是一系列的宏定义,这种实现架构能够比较好的避免缺少声明等问题。同时也较少了错误的产生可能。

上一页1 2 下一页