在我們的嵌入式開發中,使用printf列印一些資訊是一種常用的除錯手段。但是,在列印的資訊量比較多的時候,就比較難知道哪些資訊在哪個函式裡進行列印。
特別是對於異常情況的列印,我們需要快速定位到異常情況的位置。
這時候我們可以使用巨集定義來封裝乙個巨集列印函式,這個巨集列印函式可以顯示列印資訊所在的檔案、行數、函式名等資訊。如:
#define dbg_printf(fmt, args...) \
使用範例:
可見,使用方法與printf的使用方法一樣,而且每條列印語句開頭都會列印除錯資訊所在的檔名、行號、函式名資訊,方便我們查詢一些除錯資訊。
其中,__file__
、__line__
、__function__
這三個巨集是編譯器內建巨集定義,分別代表除錯資訊所在檔案、行號、函式。
除此之外,常用的巨集還有:__date__
、__time__
,分別代表當前的編譯日期與時間。如:
第二條printf中的##符號是為了處理args不代表任何引數的情況。如:
dbg_printf("hello world");
當不加##符號是,以上巨集的第二條語句被拓展為:
printf("hello world\n", );
可見,多出了乙個逗號,這個逗號是多餘的。
加上##符號後,以上巨集的第二條語句被拓展為:
printf("hello world\n");
這才是我們想要的結果。其實這些結果我們通過檢視預處理檔案可以清晰的知道:
最後需要注意的是,這個dbg_printf還是與printf不一樣的。dbg_printf巨集是兩條語句的組合,無返回值;而printf的原型是:
int printf (const char *__format, ...)
但是我們一般都很少使用printf的返回值,所以dbg_printf的用法與printf函式基本一致。
通常情況下,一些列印除錯資訊只是在我們除錯階段需要的,在程式發布階段是不需要的。
所以,為了避免列印除錯資訊帶來的資源開銷,我們可以把這些列印除錯語句給注釋掉。
一種方法是逐句進行注釋,這是一種比較低效的方法。比較高效的方法就是新增除錯巨集開關,利用條件編譯來選擇列印/不列印除錯資訊。
比如我們可以把上面的**改造為:
#define debug 1
#if debug
#define dbg_printf(fmt, args...) \
#else
#define dbg_printf(fmt, args...)
#endif
根據debug巨集的值來選擇對應的列印巨集函式。當debug的值為1時啟動相關的列印除錯語句,debug的值為0時則關閉列印除錯語句。
這樣我們就可以很方便的通過設定debug巨集的值來啟動與關閉我們整個工程的dbg_printf列印除錯資訊。
其實,上面我們封裝的列印巨集dbg_printf還有一點缺陷,比如我們與if、else使用的時候,會有這樣的一種使用情況:
此時會報語法錯誤。為什麼呢?
同樣的,我們可以先來看一下我們的demo**預處理過後,相應的巨集**會被轉換為什麼。如:
這裡我們可以看到,我們的if、else結構**被替換為如下形式:
if(c)
;else
;
顯然,出現了語法錯誤。if之後的大括號之後不能加分號,這裡的分號其實可以看做一條空語句,這個空語句會把if與else給分隔開來,導致else不能正確匹配到if,導致語法錯誤。
為了解決這個問題,有幾種方法。第一種方法是:把分號去掉。**變成:
第二種方法是:在if之後使用dbg_printf列印除錯時總是加{}。**變成:
以上兩種方法都可以正常編譯、執行了。
但是,我們c語言中,每條語句往往以分號結尾;並且,總有些人習慣在if判斷之後只有一條語句的情況下不加大括號;而且我們建立的dbg_printf巨集函式的目的就是為了對標printf函式,printf函式的使用加分號在任何地方的使用都是沒有問題的。
基於這幾個原因,我們有必要再對我們的dbg_printf巨集函式進行乙個改造。
下面引入do{}while(0)來對我們的dbg_printf進行乙個簡單的改造。改造後的dbg_printf巨集函式如下:
#define dbg_printf(fmt, args...) \
do\while(0)
這裡的do...while迴圈的迴圈體只執行一次,與不加迴圈是效果一樣。並且,可以避免了上面的問題。預處理檔案:
我們的巨集函式實體中,while(0)後面不加分號,在實際呼叫時補上分號,既符合了c語言語句分號結尾的習慣,也符合了do...while的語法規則。
使用do{}while(0)來封裝巨集函式可能會讓很多初學者看著不習慣,但必須承認的是,這確確實實是一種很常用的方法。
在stm32的hal庫中搜尋while(0):
在linux原始碼中搜尋while(0):
可見,在實際應用中,do{}while(0)用的很多。
這兩個運算子之前也有分享過,這裡順便也提一下。
#號
作為乙個預處理運算子
,可以把記號轉換成字串。
例如,如果a是乙個巨集形參,那麼#a就是轉換為字串"a"的形參名。這個過程稱為字串化(stringizing)
。以下程式演示這個過程:
##運算子可以把兩個記號組合成乙個記號。以下程式演示這個過程:
這個運算子用得很多。如:
嵌入式C語言入門 關鍵字 巨集
關鍵字extern const typedef define aad x,y x y define max x,y x y x y void test int main undef sum printf 在 d 行 n line printf 編譯的時間 s s n date time printf...
嵌入式學習(二) 嵌入式系統C 語言
1 從 cpu 復位時的指定位址開始執行 2 跳轉至彙編 startup 處執行 3 跳轉至使用者主程式 main 執行,在 main 中完成 a.初試化各硬體裝置 b.初始化各軟體模組 c.進入死迴圈 無限迴圈 d呼叫各模組的處理函式 下面是幾個 著名 的死迴圈 1 作業系統是死迴圈 2 win3...
嵌入式C語言總結
這幾天花了兩天時間看了一些嵌入式c語言方面的內容,以下是一些讀書筆記,記錄一下。1 不能有返回值 2 不能向isr 傳遞引數 3 isr盡可能的短 4 printf有重入問題 1 中斷服務程式isr 2 硬體初始化 1 某io 晶元被定為在 cpu的儲存空間而非 io空間,而且暫存器對應於某特定位址...