C C 中幾個巨集的簡單總結

2021-05-28 10:49:29 字數 4916 閱讀 9151

c/c++中幾個巨集的簡單總結

環境:vs2005 xpsp3

有人視巨集為洪水猛獸,甚至要求完全從c/c++中摒棄,有人則認為巨集為至尊寶典,在邏輯**中都大量使用。個人認為這是個仁者見仁智者見智的問題,摒棄就沒必要了,看看巨集在mfc和atl中的一些經典應用,你會發現如果不使用巨集來實現一些訊息對映和物件對映神馬的那將讓「苦逼」程式設計師多花費多少寶貴的時間。當然也不能濫用,尤其是盡量不要在邏輯**中使用,巨集中的邏輯出問題後,除錯時候的痛苦你就真的會發現原來程式設計師真的挺「苦逼」的。

其實很多人對巨集的「恐懼」可能源於下面的乙個簡單的巨集的實現:

#define sum(x, y)  x+y

看起來是正確的,但是當你這樣使用的時候sum(3, 4) * sum(2, 5),你會發現結果並不是想象的49,而是詭異的16。其實巨集僅僅完成簡單的替換,而不會像函式那樣進行引數計算並且呼叫返回,上面那個式子sum(3, 4) * sum(2, 5)實際會被替換為:3+4*2+5,現在知道為什麼結果是16了吧,那我們實現這樣應該咯:

#define sum(x, y)  (x+y)

把整個用小括號包起來,看似好像正確了,但是如果這樣呼叫sum(3||4, 4),結果會是5嗎?呃,不是,結果是1,**出了問題?我們看看展開的結果3||4+4,哦,因為加法的運算級別更高3||4+4 = 3||8 = 1,看來我們得把引數也括起來:

#define sum(x, y)  ((x)+(y))

經歷了上面的一些跌跌撞撞的失敗和嘗試後我們終於得出了乙個正確的兩個數求和的正確版本。

從上面的那個例子我想大家應該會受到一些啟發以及可以看到寫乙個巨集要注意的一些要點。不過我們今天要討論的話題則是另外幾個可能用得不多但是比較有用的巨集。

1、  怎樣通過乙個簡單的巨集得到乙個field在結構體(struct)中的偏移量 ?

你可以進行如下的定義:

#define fieldoffset(type, field)  ((unsigned int) &((type *)0)->field)

解釋:將0強制轉換為type型別指標,然後訪問field域,注意不是真的訪問,而是對它求位址,將該位址減去結構的基位址0就是偏移量了,因為基位址是0省略。

同樣得到乙個結構體中field所占用的位元組數可以定義為:

#define fieldsize(type, field)  sizeof(((type *) 0)->field)

2、  #、##和#@的用法

#把乙個巨集引數變成字串(也就是給引數加上雙引號)

##用來把巨集引數簡單連線在一起

#@把乙個巨集引數變成字元(也就是給引數加上單引號)

#define tostring(s)   #s

#define tochar(s)    #@s

#define tokenconn(s, t)  s##t

另外,對於#@如果你這樣呼叫tochar(abcd),會返回』d』,但是如果tochar(abcde),則會編譯出錯,看來這與vs的巨集處理器有關係,雖然這麼使用很詭異。

更重要的一點如果巨集定義裡用到#、#@或##的地方巨集引數可能不會像想象中展開,怎麼理解?一般來講如果巨集引數裡面有另外的巨集,會進行遞迴的替換:

#define c 7

#define b c

#define a b

#define sum(x, y)  ((x)+(y))

然後這樣呼叫int x = sum(a, a);編譯沒有問題,最後x的值是14,替換過程如下:

sum(a, a) => ((a)+(a)) => ((b)+(b)) => ((c)+(c)) => ((7)+(7))

但是如果有這樣的定義:

#define d 12

#define tostring(s)   #s

#define tokenconn(s, t)  s##t

則,tostring(d)的結果是」d」,而tokenconn(d, d)的結果則是dd,如果沒有定義dd識別符號則會編譯報錯。原因在於,巨集替換是分層次的,先替換最外層的,然後再替換引數,而這三個特殊符號都有改變巨集引數的含義的作用(把巨集引數改變為字元,字串或者不同的token),因此造成不會有多級展開。

這樣會帶來乙個問題,就是我需要用到這3個特殊的識別符號(#、#@或##),而巨集引數我又需要展開,該怎麼辦,我之前在km上看到過這樣的乙個需求,我們先看乙個我常用的巨集:

#pragma message("messageinfo: no impl, todo…")

我經常使用這個巨集插在**中來標記我沒有實現的功能或者需要後面改進的地方,這個巨集的好處是在編譯的時候,會在編譯資訊輸出視窗中顯示出你寫在message裡面的資訊。防止你寫**到後面的時候忘記哪些地方還沒有實現。但是我使用的方法很原始,如果需要實現某個功能的時候,我就去全域性搜尋那個message裡面的資訊,然後再找到地方,後來在km上面看到一種類似的需求,可以利用ide本身提供的功能直接定位到寫這段巨集的地方,如果編譯報錯的時候,你會看到類似下面的輸出:

1>f:\e\projects\tu_func\tu_func.cpp(124) : error c2065: 'dd' : undeclared identifier

此時如果你雙擊這一行,**視窗就會跳轉到編譯出錯的地方去,我們要做的就是模擬這樣的乙個輸出,利用兩個已經定義過的巨集__file__和__line__,這兩個巨集神馬作用我就不講了,我的第乙個模擬版本如下:

#define tostring(s)  #s

#define magictipsmsg(msg) message(__file__"(" tostring (__line__)"):"#msg)

結果很悲催,沒達到預期:

1>f:\e \projects\tu_func\tu_func.cpp(__line__):there is need to implement

很顯然出現了上面說到的巨集引數沒展開的問題,腫麼辦?其實解決這個問題的方法也不是很複雜,在中間加一層跳板巨集就可以了,加這個跳板巨集的用意是先把所有巨集的引數在這個跳板巨集這層裡全部展開, 那麼最終的字串轉換巨集那裡(tostring)就可以得到正確的巨集引數了。

#define  tostring(s)  #s

#define  __tostring(s)  tostring (s)

#define  magictipsmsg(msg) message(__file__"(" __tostring(__line__)"):"#msg)

現在在編譯視窗的輸出終於正確了:

1>f:\e\projects\tu_func\tu_func.cpp(121):there is need to implement

3、  幾個預定義的巨集

__line__

__file__

__date__

__time__

__cplusplu

是否全部支援上面的幾個巨集,與編譯器的具體實現有關。__line__是表示巨集所在的當前的檔案的具體行數,__file__是表示當前檔案的全路徑名,__date__巨集指令含有形式為月/日/年的串,表示原始檔被編譯時的日期。而源**編譯為目標**的時間作為串包含在__time__中。具體表現形式大家自己試一試。

在編譯c++程式時,編譯器自動定義了乙個預處理名 __cplusplus,要想知道是否define了這某個巨集怎麼辦?可以做乙個類似如下的檢查:

#ifdef  __cplusplus

#pragma message("__cplusplus definded")

#endif

4、  用#pragma匯出dll中的函式

一般我們用的比較多的匯出dll中函式的方法是使用模組定義檔案(.def),而實際上vc提供了乙個擴充套件的方法,使用__declspec(dllexport)去匯出某個函式:

int __declspec(dllexport) add(int a, int b)

此時匯出的函式名為「?add@@yahhh@z」如果我們希望進行dll動態鏈結,上面的匯出函式的名稱就有點太坑爹了,而且一旦對函式的返回值或者引數型別進行了修改,函式名也要跟著修改,我們希望匯出函式名是「add」。至少有兩種方法,一是使用def檔案來匯出,二是在函式宣告的最前面加上extern 「c」:

extern 「c」 int __declspec(dllexport) add(int a, int b)

不過,我們今天要是要討論vc 提供的另乙個預處理巨集來解決這個問題,如下:

#pragma comment(linker,"/export:add=?add@@yahhh@z ") 

這時,實際上會匯出?add@@yahhh@z和add兩個函式,但是函式的入口點是一樣的。實際上這個巨集的強大不僅僅如此,它的格式如下(msdn:

/export:entryname[,@ordinal[,noname]][,data] 

說明:@ordinal用來指定順序,noname指定只將函式匯出為序號,data 關鍵字指定匯出項為資料項。例如:

#pragma comment(linker,"/export:add=?add@@yahhh@z,@2,noname") 

就是把add函式無名匯出並且設定順序為2。

因此如果你想指定匯出的順序,或者只將函式匯出為序號,沒有函式名,這都是能夠實現的,之前和同事討論到怎麼把乙個函式無名匯出(很多系統dll的匯出表裡面就沒有匯出函式的名字),原來通過這個預處理巨集就可以簡單解決。

另乙個問題是無名匯出的函式自己怎麼動態鏈結呢(使用如下的方法,假設匯出順序為2,這種無名匯出除了增加神秘感還有神馬用途暫時還沒想到):

typedef  int (*pfunadd)(int, int);

hinstance hins = loadlibrary(text("dllname.dll"));

pfunadd pfnadd = (pfunadd)getprocaddress(hins, makeintresource(2));

int c = pfnadd(1, 2);

C C 中幾個巨集的簡單總結

有人視巨集為洪水猛獸,甚至要求完全從c c 中摒棄,有人則認為巨集為至尊寶典,在邏輯 中都大量使用。個人認為這是個仁者見仁智者見智的問題,摒棄就沒必要了,看看巨集在mfc和atl中的一些經典應用,你會發現如果不使用巨集來實現一些訊息對映和物件對映神馬的那將讓 苦逼 程式設計師多花費多少寶貴的時間。當...

C C 中巨集使用總結

博主論壇 c c 中巨集總結c程式的源 中可包括各種編譯指令,這些指令稱為預處理命令。雖然它們實際上不是c語言的一部分,但卻擴充套件了c程 序設計的環境。本節將介紹如何應用預處理程式和注釋簡化程式開發過程,並提高程式的可讀性。ansi標準定義的c語言預處理程式包括下列命令 define,error,...

C C 幾個預定義的巨集

一邊情況下,c c 編譯器會內建幾個巨集,這些巨集定義不僅可以幫助我們完成跨平台的原始碼編寫,靈活使用也可以巧妙地幫我們輸出非常有用的除錯資訊。ansi c標準中有幾個標準預定義巨集 也是常用的 編譯器在進行原始碼編譯的時候,會自動將這些巨集替換為相應內容。下面的 不僅展示了各個預定義巨集的使用,還...