博客專欄

        EEPW首頁(yè) > 博客 > 巧用宏定義,提高開(kāi)發(fā)效率

        巧用宏定義,提高開(kāi)發(fā)效率

        發(fā)布人:xiaomaidashu 時(shí)間:2022-06-22 來(lái)源:工程師 發(fā)布文章
        調(diào)試相關(guān)的宏

        開(kāi)發(fā)的過(guò)程中,常常會(huì)用到一些宏定義,很多手段可以幫助我們提高調(diào)試的效率。

        可以使用這些宏分別打印當(dāng)前源文件的信息,主要內(nèi)容是當(dāng)前的文件、當(dāng)前運(yùn)行的函數(shù)和當(dāng)前的程序行。

        具體宏如下:

        __FILE__  當(dāng)前程序源文件 (char*)
        __FUNCTION__  當(dāng)前運(yùn)行的函數(shù) (char*)
        __LINE__  當(dāng)前的函數(shù)行 (int)

        這些宏不是程序代碼定義的,而是有編譯器產(chǎn)生的。這些信息都是在編譯器處理文件的時(shí)候動(dòng)態(tài)產(chǎn)生的。

        測(cè)試示例:

        #include <stdio.h>
        
        int main(void)
        {
            printf("file: %sn", __FILE__);
            printf("function: %sn", __FUNCTION__);
            printf("line: %dn", __LINE__);
        
            return 0;
        }
        字符串化操作符

        在gcc的編譯系統(tǒng)中,可以使用#將當(dāng)前的內(nèi)容轉(zhuǎn)換成字符串。

        程序示例:

        #include <stdio.h>
        
        #define DPRINT(expr) printf("<main>%s = %dn", #expr, expr);
        
        int main(void)
        {
            int x = 3;
            int y = 5;
        
            DPRINT(x / y);
            DPRINT(x + y);
            DPRINT(x * y);
            
            return 0;
        }

        執(zhí)行結(jié)果:

        deng@itcast:~/tmp$ gcc test.c 
        deng@itcast:~/tmp$ ./a.out  
        <main>x / y = 0
        <main>x + y = 8
        <main>x * y = 15

        #expr表示根據(jù)宏中的參數(shù)(即表達(dá)式的內(nèi)容),生成一個(gè)字符串。該過(guò)程同樣是有編譯器產(chǎn)生的,編譯器在編譯源文件的時(shí)候,如果遇到了類似的宏,會(huì)自動(dòng)根據(jù)程序中表達(dá)式的內(nèi)容,生成一個(gè)字符串的宏。

        這種方式的優(yōu)點(diǎn)是可以用統(tǒng)一的方法打印表達(dá)式的內(nèi)容,在程序的調(diào)試過(guò)程中可以方便直觀的看到轉(zhuǎn)換字符串之后的表達(dá)式。具體的表達(dá)式的內(nèi)容是什么,有編譯器自動(dòng)寫(xiě)入程序中,這樣使用相同的宏打印所有表達(dá)式的字符串。

        //打印字符
        #define debugc(expr) printf("<char> %s = %cn", #expr, expr)
        //打印浮點(diǎn)數(shù)
        #define debugf(expr) printf("<float> %s = %fn", #expr, expr)
        //按照16進(jìn)制打印整數(shù)
        #define debugx(expr) printf("<int> %s = 0X%xn", #expr, expr);

        由于#expr本質(zhì)上市一個(gè)表示字符串的宏,因此在程序中也可以不適用%s打印它的內(nèi)容,而是可以將其直接與其它的字符串連接。因此,上述宏可以等價(jià)以下形式:

        //打印字符
        #define debugc(expr) printf("<char> #expr = %cn", expr)
        //打印浮點(diǎn)數(shù)
        #define debugf(expr) printf("<float> #expr = %fn", expr)
        //按照16進(jìn)制打印整數(shù)
        #define debugx(expr) printf("<int> #expr = 0X%xn", expr);

        總結(jié):

        #是C語(yǔ)言預(yù)處理階段的字符串化操作符,可將宏中的內(nèi)容轉(zhuǎn)換成字符串。

        ## 連接操作符

        在gcc的編譯系統(tǒng)中,##是C語(yǔ)言中的連接操作符,可以在編譯的預(yù)處理階段實(shí)現(xiàn)字符串連接的操作。

        程序示例:

        #include <stdio.h>
        
        #define test(x) test##x
        
        void test1(int a)
        {
            printf("test1 a = %dn", a);
        }
        
        void test2(char *s)
        {
            printf("test2 s = %sn", s);
        }
        
        int main(void)
        {
            test(1)(100);
        
            test(2)("hello world");
            
            return 0;
        }

        上述程序中,test(x)宏被定義為test##x, 他表示test字符串和x字符串的連接。

        在程序的調(diào)試語(yǔ)句中,##常用的方式如下

        #define DEBUG(fmt, args...) printf(fmt, ##args)

        替換的方式是將參數(shù)的兩個(gè)部分以##連接。##表示連接變量代表前面的參數(shù)列表。使用這種形式可以將宏的參數(shù)傳遞給一個(gè)參數(shù)。args…是宏的參數(shù),表示可變的參數(shù)列表,使用##args將其傳給printf函數(shù).

        總結(jié):

        ##是C語(yǔ)言預(yù)處理階段的連接操作符,可實(shí)現(xiàn)宏參數(shù)的連接。

        調(diào)試宏第一種形式

        一種定義的方式:

        #define DEBUG(fmt, args...)             
            {                                   
            printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);
            printf(fmt, ##args);                
            }

        程序示例:

        #include <stdio.h>
        
        #define DEBUG(fmt, args...)             
            {                                   
            printf("file:%s function: %s line: %d ", __FILE__, __FUNCTION__, __LINE__);
            printf(fmt, ##args);                
            }
        
        int main(void)
        {
            int a = 100;
            int b = 200;
        
            char *s = "hello world";
            DEBUG("a = %d b = %dn", a, b);
            DEBUG("a = %x b = %xn", a, b);
            DEBUG("s = %sn", s);
            
            return 0;
        }

        總結(jié):

        上面的DEBUG定義的方式是兩條語(yǔ)句的組合,不可能在產(chǎn)生返回值,因此不能使用它的返回值。

        調(diào)試宏的第二種定義方式

        調(diào)試宏的第二種定義方式

        #define DEBUG(fmt, args...)             
            printf("file:%s function: %s line: %d "fmt, 
            __FILE__, __FUNCTION__, __LINE__, ##args)

        程序示例

        #include <stdio.h>
        
        #define DEBUG(fmt, args...)             
            printf("file:%s function: %s line: %d "fmt, 
            __FILE__, __FUNCTION__, __LINE__, ##args)
        
        int main(void)
        {
            int a = 100;
            int b = 200;
        
            char *s = "hello world";
            DEBUG("a = %d b = %dn", a, b);
            DEBUG("a = %x b = %xn", a, b);
            DEBUG("s = %sn", s);
            
            return 0;
        }

        總結(jié):

        fmt必須是一個(gè)字符串,不能使用指針,只有這樣才可以實(shí)現(xiàn)字符串的功能。

        對(duì)調(diào)試語(yǔ)句進(jìn)行分級(jí)審查

        即使定義了調(diào)試的宏,在工程足夠大的情況下,也會(huì)導(dǎo)致在打開(kāi)宏開(kāi)關(guān)的時(shí)候在終端出現(xiàn)大量的信息。而無(wú)法區(qū)分哪些是有用的。這個(gè)時(shí)候就要加入分級(jí)檢查機(jī)制,可以定義不同的調(diào)試級(jí)別,這樣就可以對(duì)不同重要程序和不同的模塊進(jìn)行區(qū)分,需要調(diào)試哪一個(gè)模塊就可以打開(kāi)那一個(gè)模塊的調(diào)試級(jí)別。

        一般可以利用配置文件的方式顯示,其實(shí)Linux內(nèi)核也是這么做的,它把調(diào)試的等級(jí)分成了7個(gè)不同重要程度的級(jí)別,只有設(shè)定某個(gè)級(jí)別可以顯示,對(duì)應(yīng)的調(diào)試信息才會(huì)打印到終端上。

        可以寫(xiě)出一下配置文件

        [debug]
        debug_level=XXX_MODULE

        解析配置文件使用標(biāo)準(zhǔn)的字符串操作庫(kù)函數(shù)就可以獲取XXX_MODULE這個(gè)數(shù)值。

        int show_debug(int level)
        {
            if (level == XXX_MODULE)
            {
                #define DEBUG(fmt, args...)             
                printf("file:%s function: %s line: %d "fmt, 
                __FILE__, __FUNCTION__, __LINE__, ##args)       
            }
            else if (...)
            {
                ....
            }
        }
        條件編譯調(diào)試語(yǔ)句

        在實(shí)際的開(kāi)發(fā)中,一般會(huì)維護(hù)兩種源程序,一種是帶有調(diào)試語(yǔ)句的調(diào)試版本程序,另外一種是不帶有調(diào)試語(yǔ)句的發(fā)布版本程序。然后根據(jù)不同的條件編譯選項(xiàng),編譯出不同的調(diào)試版本和發(fā)布版本的程序。

        在實(shí)現(xiàn)過(guò)程中,可以使用一個(gè)調(diào)試宏來(lái)控制調(diào)試語(yǔ)句的開(kāi)關(guān)。

        #ifdef USE_DEBUG
                #define DEBUG(fmt, args...)             
                printf("file:%s function: %s line: %d "fmt, 
                __FILE__, __FUNCTION__, __LINE__, ##args)  
        #else
          #define DEBUG(fmt, args...)
        
        #endif

        如果USE_DEBUG被定義,那么有調(diào)試信息,否則DEBUG就為空。

        如果需要調(diào)試信息,就只需要在程序中更改一行就可以了。

        #define USE_DEBUG
        #undef USE_DEBUG

        定義條件編譯的方式使用一個(gè)帶有值的宏

        #if USE_DEBUG
                #define DEBUG(fmt, args...)             
                printf("file:%s function: %s line: %d "fmt, 
                __FILE__, __FUNCTION__, __LINE__, ##args)  
        #else
          #define DEBUG(fmt, args...)
        
        #endif

        可以使用如下方式進(jìn)行條件編譯

        #ifndef USE_DEBUG
        #define USE_DEBUG 0
        #endif
        使用do…while的宏定義

        使用宏定義可以將一些較為短小的功能封裝,方便使用。宏的形式和函數(shù)類似,但是可以節(jié)省函數(shù)跳轉(zhuǎn)的開(kāi)銷。如何將一個(gè)語(yǔ)句封裝成一個(gè)宏,在程序中常常使用do…while(0)的形式。

        #define HELLO(str) do { 
        printf("hello: %sn", str); 
        }while(0)

        程序示例:

        int cond = 1;
        if (cond)
            HELLO("true");
        else
            HELLO("false");
        代碼剖析

        對(duì)于比較大的程序,可以借助一些工具來(lái)首先把需要優(yōu)化的點(diǎn)清理出來(lái)。接下來(lái)我們來(lái)看看在程序執(zhí)行過(guò)程中獲取數(shù)據(jù)并進(jìn)行分析的工具:代碼剖析程序。

        測(cè)試程序:

        #include <stdio.h>
        
        #define T 100000
        
        void call_one()
        {
            int count = T * 1000;
            while(count--);
        }
        
        void call_two()
        {
            int count = T * 50;
            while(count--);
        }
        
        void call_three()
        {
            int count = T * 20;
            while(count--);
        }
        
        int main(void)
        {
            int time = 10;
        
            while(time--)
            {
                call_one();
                call_two();
                call_three();
            }
            
            return 0;
        }

        編譯的時(shí)候加入-pg選項(xiàng):

        deng@itcast:~/tmp$ gcc -pg  test.c -o test

        執(zhí)行完成后,在當(dāng)前文件中生成了一個(gè)gmon.out文件。

        deng@itcast:~/tmp$ ./test  
        deng@itcast:~/tmp$ ls
        gmon.out  test  test.c
        deng@itcast:~/tmp$

        使用gprof剖析主程序:

        deng@itcast:~/tmp$ gprof test
        Flat profile:
        
        Each sample counts as 0.01 seconds.
          %   cumulative   self              self     total           
         time   seconds   seconds    calls  ms/call  ms/call  name    
         95.64      1.61     1.61       10   160.68   160.68  call_one
          3.63      1.67     0.06       10     6.10     6.10  call_two
          2.42      1.71     0.04       10     4.07     4.07  call_three

        其中主要的信息有兩個(gè),一個(gè)是每個(gè)函數(shù)執(zhí)行的時(shí)間占程序總時(shí)間的百分比,另外一個(gè)就是函數(shù)被調(diào)用的次數(shù)。通過(guò)這些信息,可以優(yōu)化核心程序的實(shí)現(xiàn)方式來(lái)提高效率。

        當(dāng)然這個(gè)剖析程序由于它自身特性有一些限制,比較適用于運(yùn)行時(shí)間比較長(zhǎng)的程序,因?yàn)榻y(tǒng)計(jì)的時(shí)間是基于間隔計(jì)數(shù)這種機(jī)制,所以還需要考慮函數(shù)執(zhí)行的相對(duì)時(shí)間,如果程序執(zhí)行時(shí)間過(guò)短,那得到的信息是沒(méi)有任何參考意義的。

        將上訴程序時(shí)間縮短:

        #include <stdio.h>
        
        #define T 100
        
        void call_one()
        {
            int count = T * 1000;
            while(count--);
        }
        
        void call_two()
        {
            int count = T * 50;
            while(count--);
        }
        
        void call_three()
        {
            int count = T * 20;
            while(count--);
        }
        
        int main(void)
        {
            int time = 10;
        
            while(time--)
            {
                call_one();
                call_two();
                call_three();
            }
            
            return 0;
        }

        剖析結(jié)果如下:

        deng@itcast:~/tmp$ gcc -pg test.c -o test
        deng@itcast:~/tmp$ ./test  
        deng@itcast:~/tmp$ gprof test
        Flat profile:
        
        Each sample counts as 0.01 seconds.
         no time accumulated
        
          %   cumulative   self              self     total           
         time   seconds   seconds    calls  Ts/call  Ts/call  name    
          0.00      0.00     0.00       10     0.00     0.00  call_one
          0.00      0.00     0.00       10     0.00     0.00  call_three
          0.00      0.00     0.00       10     0.00     0.00  call_two

        因此該剖析程序?qū)τ谠綇?fù)雜、執(zhí)行時(shí)間越長(zhǎng)的函數(shù)也適用。

        那么是不是每個(gè)函數(shù)執(zhí)行的絕對(duì)時(shí)間越長(zhǎng),剖析顯示的時(shí)間就真的越長(zhǎng)呢?可以再看如下的例子

        #include <stdio.h>
        
        #define T 100
        
        void call_one()
        {
            int count = T * 1000;
            while(count--);
        }
        
        void call_two()
        {
            int count = T * 100000;
            while(count--);
        }
        
        void call_three()
        {
            int count = T * 20;
            while(count--);
        }
        
        int main(void)
        {
            int time = 10;
        
            while(time--)
            {
                call_one();
                call_two();
                call_three();
            }
            
            return 0;
        }

        剖析結(jié)果如下:

        deng@itcast:~/tmp$ gcc -pg test.c -o test
        deng@itcast:~/tmp$ ./test  
        deng@itcast:~/tmp$ gprof test
        Flat profile:
        
        Each sample counts as 0.01 seconds.
          %   cumulative   self              self     total           
         time   seconds   seconds    calls  ms/call  ms/call  name    
        101.69      0.15     0.15       10    15.25    15.25  call_two
          0.00      0.15     0.00       10     0.00     0.00  call_one
          0.00      0.15     0.00       10     0.00     0.00  call_three

        總結(jié):

        在使用gprof工具的時(shí)候,對(duì)于一個(gè)函數(shù)進(jìn)行g(shù)prof方式的剖析,實(shí)質(zhì)上的時(shí)間是指除去庫(kù)函數(shù)調(diào)用和系統(tǒng)調(diào)用之外,純碎應(yīng)用部分開(kāi)發(fā)的實(shí)際代碼運(yùn)行的時(shí)間,也就是說(shuō)time一項(xiàng)描述的時(shí)間值不包括庫(kù)函數(shù)printf、系統(tǒng)調(diào)用system等運(yùn)行的時(shí)間。

        這些實(shí)用庫(kù)函數(shù)的程序雖然運(yùn)行的時(shí)候?qū)⒈茸畛醯某绦驅(qū)嵱酶嗟臅r(shí)間,但是對(duì)于剖析函數(shù)來(lái)說(shuō)并沒(méi)有影響。


        *博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。

        光耦相關(guān)文章:光耦原理


        萬(wàn)用表相關(guān)文章:萬(wàn)用表怎么用


        斷路器相關(guān)文章:斷路器原理


        高壓真空斷路器相關(guān)文章:高壓真空斷路器原理
        漏電斷路器相關(guān)文章:漏電斷路器原理


        關(guān)鍵詞: 嵌入式

        相關(guān)推薦

        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 筠连县| 雅江县| 曲麻莱县| 方正县| 奉节县| 龙口市| 咸丰县| 安顺市| 桂平市| 柳州市| 成安县| 乌恰县| 永泰县| 洪江市| 剑阁县| 内乡县| 三江| 祁阳县| 修武县| 中牟县| 双柏县| 尚志市| 夏邑县| 陕西省| 将乐县| 荆门市| 县级市| 屯留县| 广西| 武陟县| 凤山市| 麻城市| 清新县| 连城县| 保靖县| 永川市| 江油市| 茌平县| 曲阳县| 乡宁县| 景谷|