C語言 嵌入式中幾個非常實用的巨集技巧

2021-10-25 03:41:58 字數 3675 閱讀 4811

在我們的嵌入式開發中,使用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空間,而且暫存器對應於某特定位址...