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

Blog


    May 25

    设计笔记——宏定义(下)

    8 根据位模式构筑图形

    这是《C Expert Programming》上的一个例子。

    这是一个惊人的#define定义的优雅集合,
    允许程序建立常量使它们看上去像是屏幕上的图形。

    #define X    )*2+1
    #define _    )*2
    #define s    ((((((((((((((((0  /* 用于建立16位宽的图形 */

    定义了它们之后,只要画所需要的图标或者图形等,程序会自动创建它们的十六进制模式。
    使用这些宏定义,程序的自描述能力大大加强:

    static unsigned short stopwatch[] =
    {
        s _ _ _ _ _ X X X X X _ _ _ X X _ ,
        s _ _ _ X X X X X X X X X _ X X X ,
        s _ _ X X X _ _ _ _ _ X X X _ X X ,
        s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
        s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
        s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
        s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
        s X X _ X X X X X _ _ _ _ _ X X _ ,
        s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
        s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
        s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
        s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
        s _ _ X X X _ _ _ _ _ X X X _ _ _ ,
        s _ _ _ X X X X X X X X X _ _ _ _ ,
        s _ _ _ _ _ X X X X X _ _ _ _ _ _ ,
        s _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    };

    C语言具有八进制、十进制和十六进制常量,但没有二进制常量,
    否则的话倒是一种更为简单的绘制图形模式的方法。

    最后,千万不要忘了在绘图结束后清除这些宏定义,
    否这很可能会给你后面的代码带来不可预测的后果。


    9 一个奇技淫巧

    这是《C Interfaces and Implementations》上的一个例子。
    在CII第四章中,对于RETURN的宏定义是:

    #define RETURN    switch(Exception_stack = Exception_stack->prev, 0) default: return

    放在如下的代码中:

    TRY
        /* do something */
        if (failed)
            RETURN 1;

    EXCETPT (Allocation_Failed)

        /* Handle allocation fail */

    END_TRY

    展开之后就是:(暂不考虑TRY的宏定义)

    TRY
        /* do something */
        if (failed)
            switch(Exception_stack = Exception_stack->prev, 0)
                default: return 1;

    EXCEPT (Allocation_Failed)

        /* Handle allocation fail */

    END_TRY

    可以看到,这个宏的唯一目的,就是在return 1之前,
    执行Exception_stack = Exception_stack->prev语句。

    仔细考虑之后可以发现,C语言中其他的结构都不能满足这一条件。
    如果使用#define RETURN if(Exception_stack = Exception_stack->prev, 1) return

    则在
    if (failed)
        RETURN 1;
    else
        ...
    语句中,会导致else的误匹配。


    10 定义多行的宏

    字符'\'可用于对一些字符进行“转义”,包括newline(指回车符)。
    被转义的newline在逻辑上把下一行当作当前行的延续,它可用于连续多行的宏定义。
    如:

    #define wdt_init() \
            WDTRST = 0x1E; \
            WDTRST = 0xE1

    则,程序中的wdt_init();将展开为:

    WDTRST = 0x1E;
    WDTRST = 0xE1;

    但这样的定义在if-else语句中展开时将产生错误。

    if (condition)
        wdt_init();
    else
        statement;

    展开后成为:

    if (condition)
        WDTRST = 0x1E;
        WDTRST = 0xE1;
    else
        statement;

    导致else失配错误。

    通常的解决方法是用do{ }while(0)结构将多行语句包起来。如:

    #define wdt_init() do { \
                WDTRST = 0x1E; \
                WDTRST = 0xE1; \
            } while (0)

    这样,上面的if-else结构展开后就成为了:

    if (condition)
        do {
            WDTRST = 0x1E;
            WDTRST = 0xE1;
        } while (0);
    else
        statement;


    11 将函数化为宏的技巧

    在嵌入式系统中,将一些简单的函数用宏代替是非常经济和实惠的。
    把函数化为宏,其基本原则是将宏实现成表达式。

    上面的那个多行的宏,实际上完全可以用逗号表达式实现:

    #define wdt_init()    (WDTRST = 0x1E, WDTRST = 0xE1)

    逗号运算符在所有运算符中优先级最低。
    其结合性从左至右,因此又称为顺序求值运算符。
    逗号表达式的值是最右边操作数的值,这也提供了一种模拟返回值的方法。
    如函数:

    uint8 flash_verify_page(void)
    {
        ECON = 0x04;
        return !ECON;
    }

    可以化为以下的宏形式:

    #define flash_verify_page()    (ECON = 0x04, !ECON)

    而调用方代码可以是:

    rc = flash_verify_page();

    也可以作为if语句的条件表达式:

    if (!flash_verify_page())
        return 0;

    另外,一些简单的选择语句可以用逻辑运算符和?:运算符实现。如:

    void timer0_set_gate(uint8 val)
    {
        if (val)
            TMOD |= 0x80;
    }

    可以化为以下的宏形式:

    #define timer0_set_gate(val)    ((val) && (TMOD |= 0x08))

    这个实现利用了&&运算符的“短路原则”,只有当val为真时,才会执行TMOD |= 0x08。

    void tic_set_tien(uint8 val)
    {
        if (val)
            TIMECON |= 0x02;
        else
            TIMECON &= 0xFD;
    }

    则可以化为以下的宏形式:

    #define tic_set_tien(val)    ((val) ? (TIMECON |= 0x02) : (TIMECON &= 0xFD))

    这里,唯一的三目运算符?:也是具有右结合性的。

    顺便强调一下,C语言中,“有些运算符的优先级是错误的”,
    “当按照常规方式使用时,可能引起误会的任何运算符”就是存在错误优先级的运算符。

    表达式中如果有布尔操作、算术运算、位操作等混合计算,始终应该在适当的地方加上括号。
    C语言中记住两个优先级就够了:乘法和除法先于加法和减法,在涉及其他操作符时一律加上括号。


    12 结语

    忠告:宏就像C语言工程师手中的一把双刃剑,如果能很好的利用它,
    它就会死心塌地的为你服务,不过可不要把自己的手给弄破了。


    参考文献:

    [1] 高质量程序设计指南——C/C++语言,林锐
    [2] C Expert Programming,Peter Van Der Linden
    [3] C Interfaces and Implementations,David R. Hanson
    [4] ANSI C中取得结构体字段偏移值的惯用法,http://blog.csdn.net/soloist/archive/2005/01/18/258654.aspx
    [5] C语言宏定义中的一个奇技淫巧,http://blog.csdn.net/myan/archive/2004/04/27/542.aspx
    [6] C语言宏定义技巧,http://blog.csdn.net/goodluckyxl/archive/2006/08/11/1050992.aspx
    [7] C宏——智者的利刃,愚者的恶梦!,http://www.vckbase.net/document/viewdoc/?id=1454

    Comments

    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

    Trackbacks

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