1. 程式人生 > >【搬運】實現一個有意思的TODO巨集

【搬運】實現一個有意思的TODO巨集

實現一個能產生warning的TODO巨集,用於在程式碼裡做備忘,效果:


下面一步步來實現這個巨集。

Let’s do it

手動讓編譯器報警(報錯)可以用以下幾個方法:

#warning sunnyxx
#error sunnyxx
#pragma message "sunnyxx"
#pragma GCC warning "sunnyxx"
#pragma GCC error "sunnyxx"

但我們知道,帶#的預處理指令是無法被#define的。好在C99提供了一個_Pragma運算子可以把部分#pragma指令字串化:

#pragma message "sunnyxx"
// 等價於 _Pragma("message \"sunnyxx\"") // 需要注意雙引號的轉義 // 或 _Pragma("message(\"sunnyxx\")") // 需要注意雙引號的轉義

利用這個特性,我們就可以將warning定義成巨集

#define SOME_WARNING _Pragma("message(\"報告大王!\")")
int main() {
    SOME_WARNING // [!]報告大王!
    return 1;
}

接下來,我們讓這個巨集能夠接受入參,並顯示到warning中去,這裡會面臨巨集的基本用法的考驗。

#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))

個人認為不太可能在一個巨集定義中完成這件事,需要用到輔助巨集:STRINGIFY(S) 將入參轉化成字串,省去了_Pragma中全串加轉義字元的困擾。
這時,一個基本功能的TODO巨集就完成了,下面向其中加入額外的資訊

// 兩個已有的巨集
#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
// 延遲1次展開的巨集
#define DEFER_STRINGIFY(S) STRINGIFY(S)
// 下面的巨集在第一行用`\`折行 #define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n" \ DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)

其中涉及到的知識:

  • 兩個常量字串可以拼接成一個整串 “123””456” => “123456”
  • 使用到3個預定義巨集__COUNTER__巨集展開次數的計數器,全域性唯一;__FILE__當前檔案完整目錄字串;__LINE__在當前檔案第幾行
  • 在字串中預定義巨集應延時展開,如果將上面的DEFER_STRINGIFY換成STRINGIFY的話,如__LINE__就不能被正確展開成行數,而是成了一個常量字串"__LINE__"
  • 為了美化,warning message中可以使用\n換行

於是,使用FORMATTED_MESSAGE(MSG)巨集就可以將帶檔案路徑、序號、行數等資訊加入到最終的warning中。

其實到這步已經OK了,為了讓這個巨集更加搶眼,還可以借鑑RAC,把巨集定義成前面加@的形式:

#define KEYWORDIFY try {} @catch (...) {}

將最終的巨集定義前面加上上面的巨集後,使用時就可以加@字首了(空的try-catch會被編譯器優化,所以沒啥效能損耗)

最終版本

#define STRINGIFY(S) #S
#define DEFER_STRINGIFY(S) STRINGIFY(S)
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
#define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n" \
DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)
#define KEYWORDIFY try {} @catch (...) {}
// 最終使用下面的巨集
#define TODO(MSG) KEYWORDIFY PRAGMA_MESSAGE(FORMATTED_MESSAGE(MSG))

What’s more

除此之外,還研究了半天如何在巨集裡面定義一個註釋,這樣就可以偷偷寫// TODO: ...的註釋,讓Xcode導航欄中也出現這個TODO了:

但很可惜沒有找到一個可行的方法,歡迎一起解決。
Xcode外掛《XTodo》也是利用這個特性,可以嘗試下。

如果需要一個產生error的巨集,將這裡替換成這樣就好了:_Pragma(STRINGIFY(GCC error(MSG)))

References