C/C++的宏定義,我想沒有多少人如今會關心這個話題。我也未曾深刻了解,可是在看C源碼時,處處是各類宏定義,菜雞我連語法都看不懂,只好來研究一下。redis
雖然Effective C++裏建議不要使用宏,而是儘可能用inline來代替宏函數,用靜態或者枚舉來代替宏定義的值,理由是宏不夠安全。不過不少源碼是用C寫的,裏面宏定義發揮了很大的做用,有些寫法很巧妙,因此宏的一些用法仍是須要了解一下。安全
宏的語法相關很少介紹,這篇文章主要描述幾種咱們平時可能沒有用到,可是源碼裏常常出現的用法。bash
在不少C源碼中都常常能夠看到do{}while(0)的寫法,在redis的源碼中也存在這樣的寫法:函數
#define dictSetVal(d, entry, _val_) do { \ if ((d)->type->valDup) \ entry->v.val = (d)->type->valDup((d)->privdata, _val_); \ else \ entry->v.val = (_val_); \ } while(0)
複製代碼
先說結論:對於宏函數老是用do{}while(0)的結構包圍起來是爲了讓宏函數老是按照預期的方案運行,不會受到分支或者其餘符號的影響ui
舉個例子,下面有個宏定義的函數spa
#define foo(x) a(x); b(x)
複製代碼
場景一:日誌
if (1)
foo(x);
宏被解析爲:
if (1)
a(x);
b(x);
複製代碼
期待的結果是,if條件知足的話就執行foo(x),也就是執行函數a和b,若是不知足就不執行。可是宏展開後的結果是,無論if條件滿不知足,都會執行函數b,由於if的執行語句沒有用大括號括起來。顯然這不是預期的結果code
你確定會想,爲何不用大括號將宏擴起來呢?orm
爲了回答這個問題,看下面這個場景:字符串
宏定義用大括號括起來
#define foo(x) { a(x); b(x) }
上面的場景宏展開後是下面這個樣子,能夠知足需求了
if (1) {
a(x);
b(x);
}
到這裏,用括號彷佛解決了問題,可是考慮下面這種寫法
if (1)
foo(x);
else
fun(x);
宏被解析爲:
if (1) {
a(x);
b(x);
};
else
fun(x)
這種狀況下,編譯就報錯了, 因此給宏加個大括號顯然不行。
複製代碼
實際上採用do while(0)就是至關於給宏加了一個大括號,並且不會出現編譯錯誤
宏定義裏#的功能是將其後面的宏參數進行字符串化操做,簡單來講就是在輸入參數的兩側分別加一個引號。 看下面的例子:
#include <stdio.h>
#define VALUE(a) do { \ printf("value is: %s\n", #a); \ } while(0)
int main() {
int i = 100;
VALUE(12);
VALUE("hello");
VALUE(i);
return 0;
}
宏定義展開後:
int main() {
int i = 100;
do { printf("value is: %s\n", "12"); } while(0);
do { printf("value is: %s\n", "\"hello\""); } while(0);
do { printf("value is: %s\n", "i"); } while(0);
return 0;
}
輸出:
value is: 12
value is: "hello"
複製代碼
兩個連續的井號##的做用是將兩個宏參數鏈接起來,看下面的例子
#include <stdio.h>
#include <stdint.h>
#define INDEX(i) index_##i
int main() {
int INDEX(1) = 1;
return 0;
}
宏定義展開後:
int main() {
int index_1 = 1;
return 0;
}
複製代碼
宏定義裏還可使用可變參數,能夠像可變參數函數裏同樣使用3個點的省略號,也能夠用一個參數標識而後再加3個點的省略號。可變數量參數函數,在日誌系統裏用的最多了。
若是使用了參數加省略號的模式,那麼這個參數就表明了整個可變參數,使用時用這個參數來表示可變參數,若是隻用了省略號來表示參數則使用默認的宏__VA_ARGS__來表示可變參數。
#include <stdio.h>
#define LOG_INFO_FORMAT "FILE:%s LN:%d FUN:%s "
#define LOG_INFO_CONTENT __FILE__, __LINE__, __func__
#define LOG(format, args...) do { \
printf(LOG_INFO_FORMAT format, LOG_INFO_CONTENT, ##args); \
printf("\n"); \
} while (0)
int main()
{
LOG("name[%s], age[%d]", "peter", 23);
LOG("END");
return 0;
}
宏展開後:
int main()
{
do { printf("FILE:%s LN:%d FUN:%s " "name[%s], age[%d]", "define_test.cpp", 39, __func__, "peter", 23); printf("\n"); } while (0);
do { printf("FILE:%s LN:%d FUN:%s " "END", "define_test.cpp", 40, __func__); printf("\n"); } while (0);
return 0;
}
輸出:
FILE:define_test.cpp LN:39 FUN:main name[peter], age[23]
FILE:define_test.cpp LN:40 FUN:main END
複製代碼
上面的宏也能夠定義爲下面的形式,效果是同樣的。
#define LOG(format, ...) do { \
printf(LOG_INFO_FORMAT format, LOG_INFO_CONTENT, ##__VA_ARGS__); \
printf("\n"); \
} while (0)
複製代碼
你確定已經注意到在宏定義的可變參數前加了##,這個目的是當可變參數個數爲0時,去掉前面的逗號。
可變參數的個數爲0時,前面的宏定義展開後的形式是
int main()
{
do { printf("FILE:%s LN:%d FUN:%s " "name[%s], age[%d]", "define_test.cpp", 39, __func__, "peter", 23); printf("\n"); } while (0);
// 若是沒有加##的話,下面這行宏展開後會多出一個逗號,這時候就會編譯報錯
do { printf("FILE:%s LN:%d FUN:%s " "END", "define_test.cpp", 40, __func__, ); printf("\n"); } while (0);
return 0;
}
複製代碼