十三誡增修--09:慎用Macro(#define)

看板C_and_CPP作者 (沒有存在感的人)時間9年前 (2016/05/23 14:59), 9年前編輯推噓3(3010)
留言13則, 4人參與, 最新討論串1/1
誡9改了很多,有錯請告知.... ======================================================== 09. 慎用macro(#define) Macro是個像鐵鎚一樣好用又危險的工具: 用得好可以釘釘子,用不好可以把釘子打彎、敲到你手指或被抓去吃子彈。 因為macro 定義出的「偽函式」有以下缺點: (1) debug會變得複雜。 (2) 無法遞迴呼叫。 (3) 無法用 & 加在 macro name 之前,取得函式位址。 (4) 沒有namespace。 (5) 可能會導致奇怪的side effect或其他無法預測的問題。 所以,使用macro前,請先確認以上的缺點是否會影響你的程式運行。 替代方案:enum(定義整數),const T(定義常數),inline function(定義函式) C++的template(定義可用不同type參數的函式), 或C++11開始的匿名函式(Lambda function)與constexpr T(編譯期常數) 以下就針對macro的缺點做說明: (1) debug會變得複雜。 編譯器不能對macro本身做語法檢查,只能檢查預處理(preprocess)後的結果。 (2) 無法遞迴呼叫。 根據C standard 6.10.3.4, 如果某macro的定義裡裏面含有跟此macro名稱同樣的的字串, 該字串將不會被預處理。 所以: #define pr(n) ((n==1)? 1 : pr(n-1)) cout<< pr(5) <<endl; 預處理過後會變成: cout<< ((5==1)? 1 : pr(5 -1)) <<endl; // pr沒有定義,編譯會出錯 (3) 無法用 & 加在 macro name 之前,取得函式位址。 因為他不是函式,所以你也不可以把函式指標套用在macro上。 (4) 沒有namespace。 錯誤例子: #define begin() x = 0 for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) // begin是std的保留字 std::cout << ' ' << *it; 改善方法:macro名稱一律用大寫,如BEGIN() (5) 可能會導致奇怪的side effect或其他無法預測的問題。 錯誤例子: #include <stdio.h> #define SQUARE(x) (x * x) int main() { printf("%d\n", SQUARE(10-5)); // 預處理後變成SQUARE(10-5*10-5) return 0; } 正確例子:在 Macro 定義中, 務必為它的參數個別加上括號 #include <stdio.h> #define SQUARE(x) ((x) * (x)) int main() { printf("%d\n", SQUARE(10-5)); return 0; } 不過遇到以下有side effect的例子就算加了括號也沒用。 錯誤例子: (感謝 yaca 網友提供) #define MACRO(x) (((x) * (x)) - ((x) * (x))) int main() { int x = 3; printf("%d\n", MACRO(++x)); // 有side effect return 0; } 備註: C++11開始支援匿名函式(Lambda function),可視情況使用。 補充資料: - http://stackoverflow.com/questions/14041453/why-are-preprocessor- macros-evil-and-what-are-the-alternatives - http://stackoverflow.com/questions/12447557/can-we-have-recursive-macros - C11 Standard 6.10.3.4 - http://en.cppreference.com/w/cpp/language/lambda -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 220.128.143.228 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1463986797.A.CC6.html

05/23 15:16, , 1F
能不用macro就不要用macro...
05/23 15:16, 1F

05/23 15:17, , 2F
有時候macro還是很重要的,例如程式版本設定
05/23 15:17, 2F

05/23 15:18, , 3F
C語言是建議用MACRO設定不會變的常數(會比const快一點)
05/23 15:18, 3F

05/23 15:23, , 4F
C++03建議用const;C++11建議用constexpr
05/23 15:23, 4F

05/23 15:23, , 5F
話說還有enum
05/23 15:23, 5F
※ 編輯: wtchen (220.128.143.228), 05/23/2016 15:26:06

05/23 15:27, , 6F
對C來說const代表coder跟system保證不會修改該variable
05/23 15:27, 6F

05/23 15:27, , 7F
可是可以用indirect的方法去改,所以不算真的"常數"
05/23 15:27, 7F

05/23 15:28, , 8F
要設常數以C直觀的作法還是#define @@
05/23 15:28, 8F

05/23 15:29, , 9F
constexpr好像只有編譯期能用....
05/23 15:29, 9F
※ 編輯: wtchen (223.136.189.53), 05/23/2016 20:44:41

05/23 21:02, , 10F
constexpr 的語意就是編譯期常數
05/23 21:02, 10F

05/23 21:04, , 11F
所以它可以用在一些需要編譯期常數的地方, 如陣列大小
05/23 21:04, 11F

05/24 23:26, , 12F
variadic macro可以拿來實作條件展開
05/24 23:26, 12F

05/24 23:28, , 13F
寫metaprogramming的時候用得著
05/24 23:28, 13F
文章代碼(AID): #1NGgfjp6 (C_and_CPP)