實現一個能產生warning的TODO宏,用於在代碼裏做備忘,效果:
下面一步步來實現這個宏。
Let’s do it
手動讓編譯器報警(報錯)可以用以下幾個方法:
#warning sunnyxx #error sunnyxx |
但我們知道,帶#
的預處理指令是無法被#define
的。好在C99提供了一個_Pragma
運算符可以把部分#pragma
指令字符串化:
// 等價於 _Pragma("message \"sunnyxx\"") // 需要注意雙引號的轉義 // 或 _Pragma("message(\"sunnyxx\")") // 需要注意雙引號的轉義 |
利用這個特性,我們就可以將warning定義成宏
#define SOME_WARNING _Pragma( 報告大王!\ )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) DEFER_STRINGIFY(__COUNTER__) MSG \ 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) DEFER_STRINGIFY(__COUNTER__) MSG \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)))
同時,上面的代碼在《github上》可以找到。也歡迎關注微博@我就叫Sunny怎麼了一起交流。
References
http://clang.llvm.org/docs/UsersManual.html
https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html