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("<main>%s=%d\n", #expr, expr);
int x= 100;
int y= 2;
dprint(x/y);
結果如下:<main>x/y=50 這說明增加了#修飾之後的運算式,即代表了將宏中的參數名直接轉換為字串
進一步,看下面的調用:
dprint(/* printf variable information */x/y);
結果如下:<main>x/y=50 這說明編譯器在產生字串的時候,不會照搬宏參數中的所有內容,注釋的內容是不會被放入字串的宏,這也是因為去注釋是編譯器預先處理階段的內容,也就是說實際的編譯過程之前,程式中注釋已經去掉。
char* a="Test string"
dprint(a) 結果如下:<main>a=4206930 這是由於a不是整數,而是字串的指標,因此列印出a的值是變數a的地址,而字串的內容依然是a。再看:
int i=1234;
dprint(1234) 結果如下:<main>1234 這說明對於直接寫入程式的數值(立即數),編譯器也可以將它的內容轉換為字串。
上面的種種說明,那種方式的優點是可以通過統一的方法列印運算式的內容。同樣可以定義如下的類似的宏,列印其它運算式的值:
#define printc(expr) printf("<char>%s = %c\n", #expr,expr);
#define printf(expr) printf("<float>%s = %f\n", #expr,expr);
#define printd(expr) printf("<int 16>%s = %d\n", #expr,expr);
在gcc的編譯器中,可以將兩個字串常量串連成一個新的字串。如下方式表示的字串是等價的:
“123456”與“123””456”
由於#expr本質就是一個字串,因此程式中也可以不使用%s列印它的內容,而是可以將其直接和其它的字串串連如下:
#define printc(expr) printf("<char>”#expr = "%c\n",expr); 其它的類似。
3)##:串連操作符
在GCC的編譯系統中,##是C語言中的串連操作符,可以在編譯的預先處理階段實現字串的串連的操作。如下所示:
#define test(x) test##x void test1(int a) { printf(”Test1!\n“); } void test2(int a) { printf(”Test2!\n“); }int main(void) { test(1); test(2); return 0; } 程式運行結果如下: Test1 Test2 這說明test(1),這說明經過宏操作,由於##串連操作符,將test(1)變成了test1。 |
在程式的調試語句中,##常用的方式如下:
#define DPRINT(fmt,args…) printf(fmt, ##args)
4)調試宏的第一種方式如下:
#define DEBUG_OUT(fmt,args…) \
{ \
printf("File:%s Function:%s Line:%d\n",__FILE__, __FUNCTON__, __LINE__); \
printf(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) do{\
printf("Hello:%s\n", str); \
}while(0)