源碼裏的宏定義用法

C/C++的宏定義,我想沒有多少人如今會關心這個話題。我也未曾深刻了解,可是在看C源碼時,處處是各類宏定義,菜雞我連語法都看不懂,只好來研究一下。redis

雖然Effective C++裏建議不要使用宏,而是儘可能用inline來代替宏函數,用靜態或者枚舉來代替宏定義的值,理由是宏不夠安全。不過不少源碼是用C寫的,裏面宏定義發揮了很大的做用,有些寫法很巧妙,因此宏的一些用法仍是須要了解一下。安全

宏的語法相關很少介紹,這篇文章主要描述幾種咱們平時可能沒有用到,可是源碼裏常常出現的用法。bash

宏定義裏的do{}while(0)有什麼用

在不少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;
}
複製代碼
相關文章
相關標籤/搜索