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