題目有點繞口……
問題見: http://bbs2.chinaunix.net/viewthread.php?tid=1373280

問題描述
/** 問題描述: */
/* 定義一個帶參數的宏 */
#define setname(name) test##name
/* 用一段文本作為該宏的參數 */
int setname(1212) = 1212;
/* 該宏會被替換為:
int test1212 = 1212;
*/
/* 現在有另一個宏 */
#define var 326
/* 如何使用讓宏var,作為另一個帶參數的宏setnaem的參數? 即:
int setname(var) = 326;
被替換為:
int test326 = 326
而不是:
int testvar = 326;
*/

問題解答
1 /** 問題解答: */
2 #include <stdio.h>
3
4 #define CONNECTION(text1,text2) text1##text2
5 #define SET_NAME(suffix) CONNECTION(test,suffix)
6
7 int main()
8 {
9 int SET_NAME(1212) = 1212;
10
11 #define VAR 326
12 int SET_NAME(VAR) = 326;
13 #undef VAR
14
15 #define VAR 86
16 int SET_NAME(VAR) = 86;
17 #undef VAR
18
19 printf("%d %d %d\n",test1212,test326,test86);
20 return 0;
21 }
22
問題分析
需要注意:
I. 宏是作文本替換
II. 替換的終止條件是:文件中不再含有宏
對第9行的:SET_NAME(1212)
1. 首先根據I和第5行,SET_NAME(1212) 會被替換成:CONNECTION(test,1212)
2. CONNECTION依然是一個宏,根據II,繼續替換
3. 根據I和第4行,CONNECTION(test,1212),被替換為 test1212
4. 所以第10行最終會被CPP替換成 "int test1212 = 1212;"
對第12行的:SET_NAME(VAR)
1. 首先根據I和第5行,SET_NAME(VAR)會被替換成:CONNECTION(test,VAR)
2. CONNECTION和VAR依然是一個宏,根據II,繼續替換
3. 根據I和第11行,CONNECTION(test,VAR)被替換為CONNECTION(test,326)
4. 再根據I和第4行,CONNECTION(test,326)被替換為test326
5. 所以第12行最終會被CPP替換成 "int test326 = 326;"
對第16行的:SET_NAME(VAR),同第12行,最終會被替換成 test86
為什么setname不行?
setname(var) 會被替換成 testvar,而后者不再含有宏,替換終止。
常見應用
根據行號命名——為了取一些相互不沖突的名字,使用行號作為后綴。
因為__LINE__也是一個宏,所以需要這種方法。
例1,Loki::ScopeGuard

Loki::ScopeGuard
/** loki/ScopeGuard.h (658) */
#define LOKI_CONCATENATE_DIRECT(s1, s2) s1##s2
#define LOKI_CONCATENATE(s1, s2) LOKI_CONCATENATE_DIRECT(s1, s2)
#define LOKI_ANONYMOUS_VARIABLE(str) LOKI_CONCATENATE(str, __LINE__)
#define LOKI_ON_BLOCK_EXIT ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeGuard
#define LOKI_ON_BLOCK_EXIT_OBJ ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeObjGuard
Loki::ScopeGuard MACRO 示例

Loki::ScopeGuard macro sample
1
// Loki::ScopeGuard macro sample
2
// Loki::ScopeGuard : 范型、輕量的RAII技術 ,對資源管理與異常安全提供非常強大的支持
3
// 該處僅演示使用__LINE__作變量后綴名的方法, 暫不討論Loki::ScopeGuard
4
5
#include <cassert>
6
#include <cstdio>
7
#include <stdexcept>
8
#include <string>
9
#include <loki::ScopeGuard>
10
11
void CopyFile(const char* input_file,const char* output_file) /* throw(std::exception) */
{
12
using namescape std;
13
14
FILE* input = fopen(input_file,"r");
15
if (!input) throw runtime_error( string("can't open input file :") + input_file);
16
LOKI_ON_BLOCK_EXIT(fclose,input);
17
18
FILE* output = fopen(output_file,"wb");
19
if (!output) throw runtime_error( string("can't open output file :") + output);
20
LOKI_ON_BLOCK_EXIT(fclose,output);
21
22
enum
{ buf_size = 1212 };
23
char buf[buf_size];
24
size_t r = buf_size;
25
26
do
{
27
r = fread(buf,1,buf_size,input);
28
if ( buf_size != fwrite(buf,1,buf_size,output)
29
throw runtime_error( string("write output file : ") + output + " occurs an error" );
30
}
31
while ( r == buf_size );
32
33
if ( !feof(input)
{
34
assert( ferror(input) );
35
throw runtime_error( string("read input file : ") + input + " occurs an error");
36
}
37
}
38
39
40
int main()
{
41
try
{
42
CopyFile("in.txt","out.txt");
43
}
44
catch (std::exception& e)
{
45
std::printf("%s\n",e.what());
46
}
47
}
48
代碼中16和20行,根據loki/ScopeGuard.h (658)中的定義,將被分別替換成:
::Loki::ScopeGuard scopeGuard16 = ::Loki::MakeGuard(fclose,input);
::Loki::ScopeGuard scopeGuard20 = ::Loki::MakeGuard(fclose,output);
也就是定義2個名字以scopeGuard為前綴文件行號為后綴的“變量”(名字就不會重復)。
它們在退出作用域的時候會分別調用:fclose(input); fclose(output);
PS:ScopeGuard的強大還不僅僅體現在這里,以后會專門介紹。
例2.1,內嵌匯編或者使用goto時,需要一個不重復的跳轉標號。

make label
#include <stdio.h>
#define CONNECTION(text1,text2) text1##text2
#define CONNECT(text1,text2) CONNECTION(text1,text2)
int main()
{
int i = 0;
CONNECT(label,1):
++i;
printf("i=%d\n",i);
if (i<326)
goto label1;
return 0;
}
例2.2,做鍵盤模擬的時候,按照i8042的規則,每次寫入端口時,需要等待輸入緩沖為空。
所以需要實現一個 KBC_Wait4IBE (key board controller wait for input buffer empty)


enum i8042 {
CMD_PORT = 0x64, // 命令端口號
DATA_PORT = 0x60, // 數據端口號
CMD_WRITE_OUTPUT_REG = 0xD2, // 準備寫數據到Output Register中
INPUT_BUFFER_FULL_BIT = 0x2,
};
void KBC_Wait4IBE() {
for (;_inp(CMD_PORT) & INPUT_BUFFER_FULL_BIT;);
// 讀取命令端口,直到INPUT_BUFFER_FULL_BIT為0,這時輸入緩沖為空。
}
void KBC_KeyDown(byte scan) {
KBC_Wait4IBE(); // 等待輸入緩沖為空
_outp(CMD_PORT,CMD_WRITE_OUTPUT_REG); // 準備寫入數據
KBC_Wait4IBE(); // 等待輸入緩沖為空
_outp(DATA_PORT,scan); // 寫入數據,鍵盤按下
}
但是又不想有函數調用消耗,所以打算用宏實現。
實驗1: 失敗的例子


#define KBC_WAIT4IBE() \
KBC_WAIT4IBE_label: \
_asm in AL,64h \
_asm TEST AL,10B \
_asm JNZ KBC_WAIT4IBE_label
void KBC_KeyDown(byte scan) {
KBC_WAIT4IBE(); // 等待輸入緩沖為空
_outp(CMD_PORT,CMD_WRITE_OUTPUT_REG); // 準備寫入數據
KBC_Wait4IBE(); //
error C2045: 'KBC_WAIT4IBE_label' : label redefined //
標號重復
}
有一個辦法就是給標號加上行號作為后綴,那么在一個文件中也不會重復(使用 #line 除外……)。


#define CONNECTION(text1,text2) text1##text2
#define CONNECT(test1,test2) CONNECTION(test1,test2)
#define KBC_WAIT4IBE_IMPL(line) \
CONNECT(KBC_WAIT4IBE_label,line): \
_asm in AL,64h \
_asm TEST AL,10B \
_asm JNZ CONNECT(KBC_WAIT4IBE_label,line)
#define KBC_WAIT4IBE() KBC_WAIT4IBE_IMPL(__LINE__)
void KBC_KeyDown(byte scan) {
KBC_WAIT4IBE(); // 等待輸入緩沖為空
_outp(CMD_PORT,CMD_WRITE_OUTPUT_REG); // 準備寫入數據
KBC_WAIT4IBE(); // 不會重復了,等待輸入緩沖為空
_outp(DATA_PORT,scan); // 寫入數據,鍵盤按下
}
btw:上面那個函數實現 KBC_Wait4IBE ,在VC8 release編譯下,會直接被inline,并且生成的代碼和KBC_WAIT4IBE完全相同……
所以,要信任編譯器的優化,不要無謂的犧牲可讀性~
重要補充! 上述解釋并不準確!!!
setname(var) 中的var同樣是一個宏,為什么不被替換?
SET_NAME(VAR)的第1次替換時,同樣VAR沒有被替換,為什么第2次替換就會被替換?
根據《代碼自動生成-宏帶來的奇技淫巧》:http://www.shnenglu.com/kevinlynx/archive/2008/03/19/44828.html
的說法,第2次替換時,涉及一個叫prescan的機制。
我平時對CPP研究不多,所以也沒弄明白這個機制。硬盤里專門講C的書也不多,我翻翻看有沒有詳細介紹的……
感興趣的讀者還可以參考: http://developer.apple.com/documentation/DeveloperTools/gcc-4.0.1/cpp/Macros.html
再補充一點: 關于于宏的調試。
在MSVC下,可以給某個編譯單元xxx.c(cpp,cxx)加入"/P"(不含引號,P一定大寫)命令。
編譯該單元后,會在xxx.c的同目錄下生成xxx.i,即預處理的結果。
在GCC下,可以使用 gcc(g++) -E xxx.c(cpp,cxx) (必要時還需要 -i ),查看預處理結果。
再次補充!:
在《The C Programming Language》 2nd Edition中找到了解釋。
附錄A.12.3 Macro Definition and Expansion p207。
以下只摘錄重點部分:
During collection(指第1次), arguments are not macro-expanded.
In both (指帶參數或者不帶參數)kinds of macro, the replacement token sequence is repeatedly rescanned for more defined identifiers.
沒能搜到ANSI C標準的文檔……
posted on 2009-02-18 23:59
OwnWaterloo 閱讀(3441)
評論(4) 編輯 收藏 引用