| 碧海潮生's profile碧海潮生的小屋PhotosBlogLists | Help |
|
May 25 设计笔记——宏定义(上)“对于宏这样的预处理器,只应该适量使用,所以无须深入讨论。” —— 《C Expert Programming》 对于PC程序来说,确实应该多用函数、少用宏。 但是在嵌入式系统中,函数调用的时间开销是不可忽视的。 一些简单的功能应尽量用宏实现。 1 包含守卫 #ifndef UART_INCLUDED #define UART_INCLUDED /* 头文件内容 */ #endif 这种通过ifndef/define/endif结构产生的预处理块的惯用法, 可以防止头(.h)文件被重复引用。 2 条件编译 #if condition1 code1 #elif condition2 code2 #else code3 #endif 这种通过条件开关控制代码是否编译的方法也是很常见的。 典型的如NDEBUG宏和assert断言,可用于Debug版程序的调试。 3 重定义数据类型 通过重新定义数据类型,可以防止由于各种平台和编译器的不同, 而产生的某些数据类型的差异,也便于代码的移植。 有观点认为通过#define指令重定义比较好, 因为“编译器对内建数据类型的处理效率高”,如: #define uint8 unsigned char #define uint16 unsigned short #define uint32 unsigned long 个人认为,这种观点并不见得有多少道理,使用比较广泛的还是typedef方式: typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned long uint32; typedef signed char int8; typedef signed short int16; typedef signed long int32; 4 求最大/小值 #define max(x, y) ((x) > (y) ? (x) : (y)) #define min(x, y) ((x) < (y) ? (x) : (y)) 宏毕竟不是函数,只是简单的文本替换。有时会带来副作用。 例如上面的两个宏,不但参数必须加括号,最外层也必须加括号。 这是为了保证宏展开后的正确性,一般教科书都有讲解,这里不作讨论。 但是即便是这样正确定义了的宏,若是使用不当,也会造成错误。 这两个宏存在的最大隐患是参数的“二次求值”,如: max(x++, y)将被展开为((x++) > (y) ? (x++) : (y)) 这样x++被求值两次,程序结果必然与预期不符。 5 求结构成员偏移量和尺寸 #define OFFSET(type, field) (size_t)&( ((type *)0)->field ) #define SIZE(type, field) sizeof( ((type *)0)->field ) ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针, 并且转换结果是一个NULL指针,因此((type *)0)的结果就是一个类型为type *的NULL指针。 如果利用这个NULL指针来访问type的成员当然是非法的, 但&( ((type *)0)->field )的意图仅仅是计算field字段的地址。 聪明的编译器根本就不生成访问type的代码, 而仅仅是根据type的内存布局和结构体实例首址在编译期计算这个(常量)地址, 这样就完全避免了通过NULL指针访问内存的问题。 又因为首址为0,所以这个地址的值就是字段相对于结构体基址的偏移。 以上方法避免了实例化一个type对象,并且求值在编译期进行,没有运行期负担。 然而,SIZE宏当中的((type *)0)->field不是访问了结构体中的成员吗? 咋看之下确实存在问题,其实玄妙之处在于sizeof运算符。 sizeof运算符是一个编译期求值的运算符,所以这个表达式在编译时已成为一个常量, 其中的((type *)0)->field在运行期根本不存在了,当然不会被求值。 因此,也不存在对NULL指针解除引用的问题了。 stddef.h中专门定义了offsetof(s, m)宏, 来求取任意一个结构类型中某个字段的偏移,并且绝大多数实现都采用了上述的方法。 6 一些精巧的实现 求比x大的最接近的base的倍数(其中,x应为非负整数,base应为正整数) #define RND(x, base) ((((x)+(base)-1) / (base)) * (base)) 求数组元素的个数 #define ARR_SIZE(a) (sizeof(a) / sizeof((a[0]))) 计算一个非负整数x对2的幂次n取模 #define MOD_POW_OF_TWO(x, n) ((x) & ((n)-1)) 判断一个正整数n是否为2的幂次 #define IS_POW_OF_TWO(n) (!((n) & ((n)-1))) 2的p次幂次的二进制表示必为最高位是1,低p位全为0;减1后则为p位,且全为1。 7 为结构提供简便的记法 下面这段代码是Net/3中mbuf的实现: struct mbuf { struct m_hdr mhdr; union { struct { struct pkthdr MH_pkthdr; /* M_PKTHDR set */ union { struct m_ext MH_ext; /* M_EXT set */ char MH_databuf[MHLEN]; } MH_dat; } MH; char M_databuf[MLEN]; /* !M_PKTHER, !M_EXT*/ } M_dat; }; 若想访问最里层的MH_databuf,则必须写成M_dat.MH.MH_dat.MH_databuf, 这样的代码很长也很难容易出错,并且阅读起来也不明了。 其实,对于MH_pkthdr、MH_ext、MH_databuf来说,虽然不是在一个结构层次上, 但是如果站在mbuf之外来看,它们都是mbuf的属性,完全可以压扁到一个平面上去看。 所以,源码中有这么一组宏: #define m_next m_hdr.mh_next #define m_len m_hdr.mh_len #define m_data m_hdr.mh_data ...... #define m_pkthdr M_dat.MH.MH_pkthdr #define m_pktdat M_dat.MH.MH_dat.MH_databuf ...... 这样写起代码来,就变得很精炼了。 Comments (3)
TrackbacksThe trackback URL for this entry is: http://jx-kingwei.spaces.live.com/blog/cns!F7A152EB74B9576E!1499.trak Weblogs that reference this entry
|
|
|