题目有点绕口……
问题见: 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
11void 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
40int 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.cppblog.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 阅读(3414)
评论(4) 编辑 收藏 引用