<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

        C/C++中宏定義的經典運用

        作者:huqin   來源:本站原創   點擊數:  更新時間:2014年03月13日   【字體:

           新年快樂,好好的玩了幾天,張家港二日游讓我有了較大的感觸,蘇南發展的真心好哦,想想老家的發展真的覺得是落后哦,回學?戳艘恍┐笈5牟┛,貌似在假期中大牛們才有時間寫博客。祝大家新年快樂,身體健康。

           言歸正傳,在C語言中宏定義是比較有用的技巧,在Linux源碼中經常使用一些宏定義,比如宏container_of()等都是經典的宏定義表示方式。在C++不再主張使用宏定義,但是宏定義實際上卻是是一個非常有用的手段。實質上宏定義能夠搞定的實現采用其它的實現也是可以的,宏定義的作用是簡單的替代作用,掌握這個是理解的關鍵,以前在沒有代碼閱讀量的時候總是以為宏定義就是簡單的定義一些常量什么的,實質上不然,宏定義完全可以寫成函數的形式,但是宏定義和函數有一定的差別,函數的調用一般采用棧的方式實現,這時候存在局部變量,形參、實參等問題,如果不理解C語言的本質,很多時候非常容易搞錯,但是宏定義則不然,宏定義沒有調用的過程,宏的實現僅僅是一個替換過程,不用壓棧出棧,宏實現的類似函數修改的就是當前區域中的變量,不是局部變量。這也是一些較深層次的問題,在剛學習C語言的時候很多人只要看見類似于函數的形式都認為是函數,實質上不一定,很有可能就是采用宏定義實現的類函數,這時候就可能導致所謂的形參實參問題發生較大的變化。關于宏的問題在面試筆試的過程中、寫代碼的過程中都是非常有用的部分,我就不再做介紹。

           今天看了一遍博客《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)

           宏定義的這種實現方式在一些嵌入式系統中是非常有效的,掌握這種實現方法能夠較好的管理各個模塊、各種狀態下對應的處理函數。這是一種經典的用法。這實際上給出可一種解決問題的模型架構,掌握好這種方式能夠較好的實現模塊的管理問題。這種實現方法不僅代碼量少,而且能夠避免很多錯誤的產生,能夠快速的進行修改,但是難點在于代碼對于初學者有一定的難度,而且宏定義實現的函數還能夠采用其他的方法實現,只是宏定義能夠較好的簡化代碼,使得代碼優美且易維護。

        關閉窗口

        相關文章

        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮