01. 調(diào)試相關(guān)的宏
在Linux使用gcc編譯程序的時候,對于調(diào)試的語句還具有一些特殊的語法。
gcc編譯的過程中,會生成一些宏,可以使用這些宏分別打印當(dāng)前源文件的信息,主要內(nèi)容是當(dāng)前的文件、當(dāng)前運行的函數(shù)和當(dāng)前的程序行。
具體宏如下:
?
__FILE__??當(dāng)前程序源文件?(char*) __FUNCTION__??當(dāng)前運行的函數(shù)?(char*) __LINE__??當(dāng)前的函數(shù)行?(int)
?
這些宏不是程序代碼定義的,而是有編譯器產(chǎn)生的。這些信息都是在編譯器處理文件的時候動態(tài)產(chǎn)生的。
「測試示例:」
?
#include?int?main(void) { ????printf("file:?%s ",?__FILE__); ????printf("function:?%s ",?__FUNCTION__); ????printf("line:?%d ",?__LINE__); ????return?0; }
?
02. # 字符串化操作符
在gcc的編譯系統(tǒng)中,可以使用#將當(dāng)前的內(nèi)容轉(zhuǎn)換成字符串。
「程序示例:」
?
#include?#define?DPRINT(expr)?printf(" %s?=?%d ",?#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??x?/?y?=?0 x?+?y?=?8 x?*?y?=?15
?
#expr表示根據(jù)宏中的參數(shù)(即表達式的內(nèi)容),生成一個字符串。該過程同樣是有編譯器產(chǎn)生的,編譯器在編譯源文件的時候,如果遇到了類似的宏,會自動根據(jù)程序中表達式的內(nèi)容,生成一個字符串的宏。
這種方式的優(yōu)點是可以用統(tǒng)一的方法打印表達式的內(nèi)容,在程序的調(diào)試過程中可以方便直觀的看到轉(zhuǎn)換字符串之后的表達式。
具體的表達式的內(nèi)容是什么,有編譯器自動寫入程序中,這樣使用相同的宏打印所有表達式的字符串。
?
//打印字符 #define?debugc(expr)?printf("?%s?=?%c ",?#expr,?expr) //打印浮點數(shù) #define?debugf(expr)?printf(" ?%s?=?%f ",?#expr,?expr) //按照16進制打印整數(shù) #define?debugx(expr)?printf(" ?%s?=?0X%x ",?#expr,?expr);
?
由于#expr本質(zhì)上市一個表示字符串的宏,因此在程序中也可以不適用%s打印它的內(nèi)容,而是可以將其直接與其它的字符串連接。
因此,上述宏可以等價以下形式:
?
//打印字符 #define?debugc(expr)?printf("?#expr?=?%c ",?expr) //打印浮點數(shù) #define?debugf(expr)?printf(" ?#expr?=?%f ",?expr) //按照16進制打印整數(shù) #define?debugx(expr)?printf(" ?#expr?=?0X%x ",?expr);
?
「總結(jié):」
#是C語言預(yù)處理階段的字符串化操作符,可將宏中的內(nèi)容轉(zhuǎn)換成字符串。
03. ## 連接操作符
在gcc的編譯系統(tǒng)中,##是C語言中的連接操作符,可以在編譯的預(yù)處理階段實現(xiàn)字符串連接的操作。
「程序示例:」
?
#include?#define?test(x)?test##x void?test1(int?a) { ????printf("test1?a?=?%d ",?a); } void?test2(char?*s) { ????printf("test2?s?=?%s ",?s); } int?main(void) { ????test(1)(100); ????test(2)("hello?world"); ???? ????return?0; }
?
上述程序中,test(x)宏被定義為test##x, 他表示test字符串和x字符串的連接。
在程序的調(diào)試語句中,##常用的方式如下
?
#define?DEBUG(fmt,?args...)?printf(fmt,?##args)
?
替換的方式是將參數(shù)的兩個部分以##連接。##表示連接變量代表前面的參數(shù)列表。使用這種形式可以將宏的參數(shù)傳遞給一個參數(shù)。args…是宏的參數(shù),表示可變的參數(shù)列表,使用##args將其傳給printf函數(shù).
「總結(jié):」
##是C語言預(yù)處理階段的連接操作符,可實現(xiàn)宏參數(shù)的連接。
04. 調(diào)試宏第一種形式
一種定義的方式:
?
#define?DEBUG(fmt,?args...)????????????? ????{??????????????????????????????????? ????printf("file:%s?function:?%s?line:?%d?",?__FILE__,?__FUNCTION__,?__LINE__); ????printf(fmt,?##args);???????????????? ????}
?
「程序示例:」
?
#include?#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?=?%d ",?a,?b); ????DEBUG("a?=?%x?b?=?%x ",?a,?b); ????DEBUG("s?=?%s ",?s); ???? ????return?0; }
?
「總結(jié):」
上面的DEBUG定義的方式是兩條語句的組合,不可能在產(chǎn)生返回值,因此不能使用它的返回值。
05. 調(diào)試宏的第二種定義方式
調(diào)試宏的第二種定義方式
?
#define?DEBUG(fmt,?args...)????????????? ????printf("file:%s?function:?%s?line:?%d?"fmt,? ????__FILE__,?__FUNCTION__,?__LINE__,?##args)
?
程序示例
?
#include?#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?=?%d ",?a,?b); ????DEBUG("a?=?%x?b?=?%x ",?a,?b); ????DEBUG("s?=?%s ",?s); ???? ????return?0; }
?
「總結(jié):」
fmt必須是一個字符串,不能使用指針,只有這樣才可以實現(xiàn)字符串的功能。
06. 對調(diào)試語句進行分級審查
即使定義了調(diào)試的宏,在工程足夠大的情況下,也會導(dǎo)致在打開宏開關(guān)的時候在終端出現(xiàn)大量的信息。而無法區(qū)分哪些是有用的。
這個時候就要加入分級檢查機制,可以定義不同的調(diào)試級別,這樣就可以對不同重要程序和不同的模塊進行區(qū)分,需要調(diào)試哪一個模塊就可以打開那一個模塊的調(diào)試級別。
一般可以利用配置文件的方式顯示,其實Linux內(nèi)核也是這么做的,它把調(diào)試的等級分成了7個不同重要程度的級別,只有設(shè)定某個級別可以顯示,對應(yīng)的調(diào)試信息才會打印到終端上。
可以寫出一下配置文件
?
[debug] debug_level=XXX_MODULE
?
解析配置文件使用標(biāo)準(zhǔn)的字符串操作庫函數(shù)就可以獲取XXX_MODULE這個數(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?(...) ????{ ????????.... ????} }
?
07. 條件編譯調(diào)試語句
在實際的開發(fā)中,一般會維護兩種源程序,一種是帶有調(diào)試語句的調(diào)試版本程序,另外一種是不帶有調(diào)試語句的發(fā)布版本程序。
然后根據(jù)不同的條件編譯選項,編譯出不同的調(diào)試版本和發(fā)布版本的程序。
在實現(xiàn)過程中,可以使用一個調(diào)試宏來控制調(diào)試語句的開關(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
?
定義條件編譯的方式使用一個帶有值的宏
?
#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
?
可以使用如下方式進行條件編譯
?
#ifndef?USE_DEBUG #define?USE_DEBUG?0 #endif
?
08. 使用do…while的宏定義
使用宏定義可以將一些較為短小的功能封裝,方便使用。宏的形式和函數(shù)類似,但是可以節(jié)省函數(shù)跳轉(zhuǎn)的開銷。
如何將一個語句封裝成一個宏,在程序中常常使用do…while(0)的形式。
?
#define?HELLO(str)?do?{? printf("hello:?%s ",?str);? }while(0)
?
「程序示例:」
?
int?cond?=?1; if?(cond) ????HELLO("true"); else ????HELLO("false");
?
09. 代碼剖析
對于比較大的程序,可以借助一些工具來首先把需要優(yōu)化的點清理出來。接下來我們來看看在程序執(zhí)行過程中獲取數(shù)據(jù)并進行分析的工具:代碼剖析程序。
「測試程序:」
?
#include?#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; }
?
編譯的時候加入-pg選項:
?
deng@itcast:~/tmp$?gcc?-pg??test.c?-o?test
?
執(zhí)行完成后,在當(dāng)前文件中生成了一個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
?
其中主要的信息有兩個,一個是每個函數(shù)執(zhí)行的時間占程序總時間的百分比,另外一個就是函數(shù)被調(diào)用的次數(shù)。通過這些信息,可以優(yōu)化核心程序的實現(xiàn)方式來提高效率。
當(dāng)然這個剖析程序由于它自身特性有一些限制,比較適用于運行時間比較長的程序,因為統(tǒng)計的時間是基于間隔計數(shù)這種機制,所以還需要考慮函數(shù)執(zhí)行的相對時間,如果程序執(zhí)行時間過短,那得到的信息是沒有任何參考意義的。
「將上訴程序時間縮短:」
?
#include?#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ù)也適用。
那么是不是每個函數(shù)執(zhí)行的絕對時間越長,剖析顯示的時間就真的越長呢?可以再看如下的例子
?
#include?#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ù)進行g(shù)prof方式的剖析,實質(zhì)上的時間是指除去庫函數(shù)調(diào)用和系統(tǒng)調(diào)用之外,純碎應(yīng)用部分開發(fā)的實際代碼運行的時間,也就是說time一項描述的時間值不包括庫函數(shù)printf、系統(tǒng)調(diào)用system等運行的時間。
這些實用庫函數(shù)的程序雖然運行的時候?qū)⒈茸畛醯某绦驅(qū)嵱酶嗟臅r間,但是對于剖析函數(shù)來說并沒有影響。
審核編輯:劉清
評論
查看更多