使用宏作宏参数

题目有点绕口……

问题见: http://bbs2.chinaunix.net/viewthread.php?tid=1373280

 

ExpandedBlockStart.gif
/** 问题描述: */

/* 定义一个带参数的宏 */
#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;
*/

 

ExpandedBlockStart.gif
 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. CONNECTIONVAR依然是一个宏,根据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

ExpandedBlockStart.gif
/** 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 示例

ExpandedBlockStart.gif
 1None.gif// Loki::ScopeGuard macro sample
 2None.gif// Loki::ScopeGuard : 范型、轻量的RAII技术 ,对资源管理与异常安全提供非常强大的支持
 3None.gif// 该处仅演示使用__LINE__作变量后缀名的方法, 暂不讨论Loki::ScopeGuard
 4None.gif
 5None.gif#include <cassert>
 6None.gif#include <cstdio>
 7None.gif#include <stdexcept>
 8None.gif#include <string>
 9None.gif#include <loki::ScopeGuard>
10None.gif
11ExpandedBlockStart.gifvoid CopyFile(const char* input_file,const char* output_file) /* throw(std::exception) */ {
12InBlock.gif    using namescape std;
13InBlock.gif
14InBlock.gif    FILE* input = fopen(input_file,"r");
15InBlock.gif    if (!input) throw runtime_error( string("can't open input file :"+ input_file);
16InBlock.gif    LOKI_ON_BLOCK_EXIT(fclose,input);
17InBlock.gif
18InBlock.gif    FILE* output = fopen(output_file,"wb");
19InBlock.gif    if (!output) throw runtime_error( string("can't open output file :"+ output);
20InBlock.gif    LOKI_ON_BLOCK_EXIT(fclose,output);
21InBlock.gif
22ExpandedSubBlockStart.gif    enum { buf_size = 1212 };
23InBlock.gif    char buf[buf_size];
24InBlock.gif    size_t r = buf_size;
25InBlock.gif
26ExpandedSubBlockStart.gif    do {
27InBlock.gif        r = fread(buf,1,buf_size,input);
28InBlock.gif        if ( buf_size != fwrite(buf,1,buf_size,output)
29InBlock.gif            throw runtime_error( string("write output file : "+ output + " occurs an error" );
30ExpandedSubBlockEnd.gif    }
31InBlock.gif    while ( r == buf_size );
32InBlock.gif
33ExpandedSubBlockStart.gif    if ( !feof(input) {
34InBlock.gif        assert( ferror(input) );
35InBlock.gif        throw runtime_error( string("read input file : "+ input + " occurs an error");
36ExpandedSubBlockEnd.gif    }
37ExpandedBlockEnd.gif}
38None.gif
39None.gif
40ExpandedBlockStart.gifint main() {
41ContractedSubBlock.gif    try dot.gif
44ContractedSubBlock.gif    catch (std::exception& e) dot.gif
47ExpandedBlockEnd.gif}
48None.gif


 代码中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时,需要一个不重复的跳转标号。

ExpandedBlockStart.gif
#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)

ExpandedBlockStart.gif
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: 失败的例子

ExpandedBlockStart.gif
#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 除外……)。

ExpandedBlockStart.gif
#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

转载于:https://my.oschina.net/qihh/blog/56680

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值