碧海潮生's profile碧海潮生的小屋PhotosBlogLists Tools Help

Blog


    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)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    Oct. 21
    Oct. 21
    No namewrote:
    Welcome to enter (wow gold) and (wow power leveling) trading site, (wow gold) are cheap, (wow power leveling) credibility Very good! Quickly into the next single! Key words directly to the website click on transactions! -25617392303730
    Aug. 5

    Trackbacks

    The trackback URL for this entry is:
    http://jx-kingwei.spaces.live.com/blog/cns!F7A152EB74B9576E!1499.trak
    Weblogs that reference this entry
    • None