1)列印檔案,函式和程式行
在linux使用gcc編譯程式的時候,gcc在編譯的過程中,會生成一些巨集,可以使用這些巨集分別列印當前原始檔的資訊。主要有當前檔案(__file__,為字串型char *),當前執行的函式(__function__,為字串型char *)和當前的程式行(__line__,為整型int)。使用例項如下:
printf("file:%s function:%s line:%d\n",__file__, __functon__, __line); 注意以上三個巨集都是在編譯的時候產生的巨集,而不是變數。
2)#:字串化操作符
在gcc的編譯系統中,可以使用#將當前的內容轉換成字串。例如:#define dprint(expr) printf("%s=%d\n", #expr, expr);
int x= 100;
int y= 2;
dprint(x/y);
結果如下:x/y=50 這說明增加了#修飾之後的表示式,即代表了將巨集中的引數名直接轉換為字串
進一步,看下面的呼叫:
dprint(/* printf variable information */x/y);
結果如下:x/y=50 這說明編譯器在生成字串的時候,不會照搬巨集引數中的所有內容,注釋的內容是不會被放入字串的巨集,這也是因為去注釋是編譯器預處理階段的內容,也就是說實際的編譯過程之前,程式中注釋已經去掉。
char* a="test string"
dprint(a) 結果如下:a=4206930 這是由於a不是整數,而是字串的指標,因此列印出a的值是變數a的位址,而字串的內容依然是a。再看:
int i=1234;
dprint(1234) 結果如下:1234 這說明對於直接寫入程式的數值(立即數),編譯器也可以將它的內容轉換為字串。
上面的種種說明,那種方式的優點是可以通過統一的方法列印表示式的內容。同樣可以定義如下的類似的巨集,列印其它表示式的值:
#define printc(expr) printf("%s = %c\n", #expr,expr);
#define printf(expr) printf("%s = %f\n", #expr,expr);
#define printd(expr) printf("%s = %d\n", #expr,expr);
在gcc的編譯器中,可以將兩個字串常量連線成乙個新的字串。如下方式表示的字串是等價的:
「123456」與「123」」456」
由於#expr本質就是乙個字串,因此程式中也可以不使用%s列印它的內容,而是可以將其直接和其它的字串連線如下:
#define printc(expr) printf("」#expr = "%c\n",expr); 其它的類似。
3)##:連線操作符
在gcc的編譯系統中,##是c語言中的連線操作符,可以在編譯的預處理階段實現字串的連線的操作。如下所示:
#define test(x) test##x
void test1(int a)
void test2(int a)
int main(void)
程式執行結果如下:
test1
test2
這說明test(1),這說明經過巨集操作,由於##連線操作符,將test(1)變成了test1。
在程式的除錯語句中,##常用的方式如下:
#define dprint(fmt,args…) printf(fmt, ##args)
4)除錯巨集的第一種方式如下:
#define debug_out(fmt,args…) \
在巨集定義中,在每一行的結尾使用\表示下一行的內容是與上面連續的。經過這樣處理的好處是可以在每行除錯語句的前面列印出附加的資訊,經過這樣的巨集處理後,debug_out的方式和printf完全一致,可以支援可變引數列表和格式化輸出,只是附加了一段當前檔案,當前函式,當前行的資訊。呼叫巨集的方法,可以加語句結束的標誌(;),這樣像是函式呼叫,其實不是。這時的;表示程式多出來了一條空語句。如果不加的話, 也沒錯,巨集呼叫本來就不加嘛,是吧?
但存在問題或者存在一些缺陷,實質是printf是乙個有int返回值的函式,只是很少用而已。但根據debug_out的定義,不可能再產生返回值,在絕大數的實際情況下 由於不用返回值,所以這還是挺常用的。
4)除錯巨集的第二種方式如下:
#define debug_out(fmt,args…) printf("file:%s function:%s line:%d"fmt, __file__, __functon__, __line__, ##args)
這個就解決了無法獲得printf返回值的弊端。但同時卻有另外乙個弊端。這是由於printf函式的第乙個引數是const char *格式,在使用的過程中,經常使用固定字串表示格式輸入引數。其實printf也可以使用變數作為第乙個引數輸入如下:
const char *s = "string";
printf(s);
以上格式是合法的,輸出的結果就是s指向的字串的值string。然而在上述debug_out定義中,fmt必須是乙個字串,不能使用指標,只有這樣才能實現字串連線的功能。因此debug_out(s)是不合法的。
5)使用乙個變數use_debug然後用ifdef use_debug來開關控制除錯巨集。這樣就不用在最終的發行版中為去掉除錯巨集而苦惱了。當然也可以use_debug定義乙個值,當值不同時,根據實際情況使用不同的除錯巨集。
6)使用do…while的定義
使用巨集定義可以將一些較為短小的功能封裝,方便使用。巨集的形式和函式類似,但是使用巨集的話,就可以節省函式跳轉的開銷。在程式中常常使用do…while(0)來封裝成乙個巨集。例如:
#define hello(str) dowhile(0)
C語言中程式除錯和巨集使用技巧
1 列印檔案,函式和程式行 在linux使用gcc編譯程式的時候,gcc在編譯的過程中,會生成一些巨集,可以使用這些巨集分別列印當前原始檔的資訊。主要有當前檔案 file 為字串型char 當前執行的函式 function 為字串型char 和當前的程式行 line 為整型int 使用例項如下 pr...
C語言中如何使用巨集
c 和c 中的巨集 macro 屬於編譯器預處理的範疇,屬於編譯期概念 而非執行期概念 下面對常遇到的巨集的使用問題做了簡單總結。巨集使用中的常見的基礎問題 符號和 符號的使用 符號的使用 巨集的解釋方法 我們能碰到的巨集的使用 巨集使用中的陷阱 常見的基礎性問題 關於 和 在c語言的巨集中,的功能...
C語言中除錯用到的巨集定義
在linux程式設計中,當檔案數量變的眾多之後,使用gdb除錯就是一場災難。因此在程式中加入合理的列印資訊,定位錯誤出現的檔名,函式名,行號等資訊,能更高效的定位到問題的所在。下面定義了巨集,分別是warning,info,error,show time,debug等。利用了 file functi...