2008-10-19 61 views
82

正如很多我以前提到的問題,我到K & R工作組,和我目前進入預處理。其中一個更有趣的事情—的東西我從來沒有從任何我以前嘗試之前就知道學習C —是##預處理程序操作。可以根據K & R:什麼是##預處理操作符和陷阱要考慮的應用程序?

預處理操作者## 提供了一種方法宏膨脹期間來連接實際 參數。如果在替換文本一個 參數是相鄰 到##,該參數是由 實際參數替換時, ##和周圍的白色空間中 去除,並且將結果重新掃描。 例如,宏paste 會將其兩個參數:

#define paste(front, back) front ## back

所以paste(name, 1)創建令牌 name1

如何以及爲什麼有人會在現實世界中使用它?什麼是它的使用的實際例子,有什麼需要考慮的?

回答

44

CrashRpt:使用##到宏觀的多字節字符串轉換爲Unicode

在CrashRpt(崩潰報告庫)一個有趣的用法如下:

#define WIDEN2(x) L ## x 
#define WIDEN(x) WIDEN2(x) 
//Note you need a WIDEN2 so that __DATE__ will evaluate first. 

在這裏,他們想用兩個字節的字符串,而不是一個字節的每字符的字符串。這可能看起來像是毫無意義,但他們這樣做是有原因的。

std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__); 

它們將它與另一個使用日期和時間返回字符串的宏一起使用。

L旁邊__ DATE __會給你一個編譯錯誤。


的Windows:使用##對通用的Unicode或多字節字符串

Windows使用類似如下:

#ifdef _UNICODE 
    #define _T(x)  L ## x 
#else 
    #define _T(x) x 
#endif 

而且_T到處使用代碼


個各種庫,使用乾淨的訪問和修改名稱:

我也看到了它的代碼用來定義存取和修改器:

#define MYLIB_ACCESSOR(name) (Get##name) 
#define MYLIB_MODIFIER(name) (Set##name) 

同樣可以使用任何其他類型的同樣的方法聰明的名字創作。


各種庫,用它立刻做出幾個變量聲明:

#define CREATE_3_VARS(name) name##1, name##2, name##3 
int CREATE_3_VARS(myInts); 
myInts1 = 13; 
myInts2 = 19; 
myInts3 = 77; 
+3

既然你可以連接在編譯時字符串文字,你可以在創建日期表達減少'的std :: wstring的創建日期=擴大( __DATE__)L「」WIDEN(__ TIME __);`並立即隱式構建整個字符串。 – user666412 2016-02-15 17:54:19

1

我用它來添加自定義前綴由宏定義的變量。因此,像:

UNITTEST(test_name) 

擴展爲:

void __testframework_test_name() 
3

,當您需要連接別的東西宏觀參數您可以使用標記粘貼。

它可用於模板:

#define LINKED_LIST(A) struct list##_##A {\ 
A value; \ 
struct list##_##A *next; \ 
}; 

在這種情況下LINKED_LIST(INT)會給你

struct list_int { 
int value; 
struct list_int *next; 
}; 

同樣你可以寫列表遍歷一個函數模板。

6

這在各種情況下都很有用,以免不必要地重複自己。以下是Emacs源代碼的一個例子。我們想從庫中加載一些函數。函數「foo」應該分配給fn_foo,依此類推。我們定義下面的宏:

#define LOAD_IMGLIB_FN(lib,func) {          \ 
    fn_##func = (void *) GetProcAddress (lib, #func);     \ 
    if (!fn_##func) return 0;           \ 
    } 

然後,我們可以使用它:

LOAD_IMGLIB_FN (library, XpmFreeAttributes); 
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); 
LOAD_IMGLIB_FN (library, XpmReadFileToImage); 
LOAD_IMGLIB_FN (library, XImageFree); 

的好處是不必寫兩個fn_XpmFreeAttributes"XpmFreeAttributes"(和風險拼錯其中之一)。

1

主要用途是當您有一個命名約定,並且希望您的宏利用該命名約定。也許你有幾個方法家族:image_create(),image_activate()和image_release()也有file_create(),file_activate(),file_release()和mobile_create(),mobile_activate()和mobile_release()。

你可以寫一個宏處理對象的生命週期:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release()) 

當然,一種「對象的最小版本」是不是唯一的排序命名約定適用於 - 幾乎絕大多數命名約定使用公共子字符串來形成名稱。它可以使我的函數名稱(如上所述),或者字段名稱,變量名稱或其他任何東西。

2

我在C程序中使用它來幫助正確執行一系列必須符合某種調用約定的方法的原型。在某種程度上,這可以用於傳統的C窮人的面向對象:

SCREEN_HANDLER(activeCall) 

擴展到這樣的事情:

STATUS activeCall_constructor(HANDLE *pInst) 
STATUS activeCall_eventHandler(HANDLE *pInst, TOKEN *pEvent); 
STATUS activeCall_destructor(HANDLE *pInst); 

這加強了所有正確的參數設置「導出」當你做對象:

SCREEN_HANDLER(activeCall) 
SCREEN_HANDLER(ringingCall) 
SCREEN_HANDLER(heldCall) 

上述在頭文件等,這也是維護有用的,如果你連碰巧要更改的定義和/或方法添加到「對象」。

2

SGlib使用##來基本上模糊C中的模板。因爲沒有函數重載,所以##用於將類型名稱粘貼到生成函數的名稱中。如果我有一個名爲list_t的列表類型,那麼我會得到像sglib_list_t_concat這樣的函數,依此類推。

0

這對記錄非常有用。你可以這樣做:

#define LOG(msg) log_msg(__function__, ## msg) 

或者,如果你的編譯器不支持功能FUNC

#define LOG(msg) log_msg(__file__, __line__, ## msg) 

上述的「功能」記錄信息,準確地顯示其功能記錄一條消息。

我的C++語法可能不完全正確。

+1

你想要做什麼?如果沒有「##」,它也可以工作,因爲不需要將令牌粘貼到「msg」。你是否想把msg串起來?另外,__FILE__和__LINE__必須大寫,而不是小寫。 – bk1e 2008-10-19 20:44:16

+0

你說得對。我需要找到原始腳本來查看##是如何使用的。對我感到羞恥,今天沒有餅乾! – ya23 2008-12-12 09:59:59

14

下面是我升級到編譯器的新版本時,碰到了一個疑難雜症:

不必要使用標記粘貼運算符(##)是不可移植的,可能產生不希望的空白,警告或錯誤。

當令牌粘貼操作符的結果不是有效的預處理令牌時,令牌粘貼操作符是不必要的,並且可能有害。

例如,一個可以嘗試使用該令牌粘貼操作在編譯時建立字符串文字:

#define STRINGIFY(x) #x 
#define PLUS(a, b) STRINGIFY(a##+##b) 
#define NS(a, b) STRINGIFY(a##::##b) 
printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

在一些編譯器,這將輸出預期的結果:

1+2 std::vector 

在其他編譯器,這將包括不需要的空白:

1 + 2 std :: vector 

相當現代的版本GCC的附加組件(> = 3.3左右)將無法被編譯的代碼:

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token 
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token 
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token 
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token 

的解決方案是串聯預處理器令牌C/C++運營商時,可以省略令牌粘貼操作者:

#define STRINGIFY(x) #x 
#define PLUS(a, b) STRINGIFY(a+b) 
#define NS(a, b) STRINGIFY(a::b) 
printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

GCC CPP documentation chapter on concatenation在令牌粘貼操作符上有更多有用的信息。

+0

謝謝 - 我沒有意識到這一點(但後來我不太用這些預處理操作符...)。 – 2008-10-20 01:12:48

+3

由於某種原因,它被稱爲「令牌粘貼」操作符 - 當您完成時,意圖是以單個令牌結束。很好的寫作。 – 2009-04-30 18:17:49

2

我用它爲家庭推出斷言在非標準的C編譯器嵌入:

 


#define ASSERT(exp) if(!(exp)){ \ 
         print_to_rs232("Assert failed: " ## #exp);\ 
         while(1){} //Let the watchdog kill us 

 
4

在堆棧中的前一個問題 溢出問生成枚舉常量字符串表示的平滑方法沒有很多容易出錯的重新輸入。

Link

我回答這個問題,展示瞭如何運用小魔預處理器可以讓你像這樣定義你的枚舉(例如)...;

ENUM_BEGIN(Color) 
    ENUM(RED), 
    ENUM(GREEN), 
    ENUM(BLUE) 
ENUM_END(Color) 

...隨着益處,即宏擴展不僅定義枚舉(在.h文件中),它也定義串的匹配陣列(在.c文件);

const char *ColorStringTable[] = 
{ 
    "RED", 
    "GREEN", 
    "BLUE" 
}; 

字符串表的名稱來自使用##運算符將宏參數(即Color)粘貼到StringTable。像這樣的應用程序(技巧?)是#和##運算符非常重要的地方。

46

當您使用令牌貼('##')或字符串('#')預處理操作符時,需要注意的一件事是您必須使用額外的間接級別以使它們正常工作所有情況。

如果你不這樣做,並傳遞給標記粘貼操作的項目是宏自己,你會得到的結果是可能不是你想要什麼:

#include <stdio.h> 

#define STRINGIFY2(x) #x 
#define STRINGIFY(x) STRINGIFY2(x) 
#define PASTE2(a, b) a##b 
#define PASTE(a, b) PASTE2(a, b) 

#define BAD_PASTE(x,y) x##y 
#define BAD_STRINGIFY(x) #x 

#define SOME_MACRO function_name 

int main() 
{ 
    printf("buggy results:\n"); 
    printf("%s\n", STRINGIFY(BAD_PASTE(SOME_MACRO, __LINE__))); 
    printf("%s\n", BAD_STRINGIFY(BAD_PASTE(SOME_MACRO, __LINE__))); 
    printf("%s\n", BAD_STRINGIFY(PASTE(SOME_MACRO, __LINE__))); 

    printf("\n" "desired result:\n"); 
    printf("%s\n", STRINGIFY(PASTE(SOME_MACRO, __LINE__))); 
} 

輸出:

buggy results: 
SOME_MACRO__LINE__ 
BAD_PASTE(SOME_MACRO, __LINE__) 
PASTE(SOME_MACRO, __LINE__) 

desired result: 
function_name21 
1

在WinCE的一個重要用途:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT)) 

在定義寄存器位描述我們做以下:

#define ADDR_LEFTSHIFT       0 

#define ADDR_WIDTH        7 

,同時使用BITFMASK,只需使用:

BITFMASK(ADDR) 
相關問題