C语言宏定义中的迷惑行为
Posted 嵌入式软件实战派
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言宏定义中的迷惑行为相关的知识,希望对你有一定的参考价值。
问题
以下这段代码运行后输出什么结果?
#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)
printf("h(f(1,2))-> %s, g(f(1,2))-> %s\\n", h(f(1,2)), g(f(1,2)));
先上答案:
h(f(1,2))-> 12, g(f(1,2))-> f(1,2)
也许你跟我一样,满脑子的“为什么“?
宏定义中的#
和##
(对于已经理解这两个符号的同学,建议跳过)
首先说一下:
#
是将内容字符串化##
是连接字符串
直接在tutorialspoint找两个例子说明下:
例1:
#include <stdio.h>
#define message_for(a, b) \\
printf(#a " and " #b ": We love you!\\n")
int main(void) {
message_for(Carole, Debra);
return 0;
}
输出结果是:
Carole and Debra: We love you!
例2:
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void) {
int token34 = 40;
tokenpaster(34);
return 0;
}
输出结果是:
token34 = 40
tokenpaster(34);
这个通过预处理后就变成了printf ("token34 = %d", token34)
到这来,我想大家基本上理解这#
和##
是什么意思了吧。
宏替换
回到文章开始的问题,为什么h(f(1,2))
和g(f(1,2))
的结果是不一样的?这就要看宏替换的规则是什么了。
这个题目在网上能搜出一堆博文来,很多都没讲透。于是,我翻C99标准《ISO/IEC 9899:1999 (E)》,其中有一个规则是这样的:
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
这段话不好理解,以前在学校上学的阅读理解能力就要发挥出来了,我大致将其拆解来讲解下:
- 首先要identify这个是function-like macro,即识别出这是一个函数式宏;
- 处理函数式宏里面的参数parameter,这里有个条件,在替换这些参数宏的时候,除非遇到
#
或##
这样的东西,否则就将这些parameter替换到底; - 待所有的parameter都替换完(或者
#
或##
处理完),预处理器还会对整个宏剩下的继续处理,因为这个function-like macro会替换出一些新东西,例如h( f(1,2) )
–>g(12)
,g(12)
-->"12"
。
于是,按以上这个套路将上面的问题解析一遍:
先看h(f(1,2))
:
h(f(1,2))
预处理先找到h
这个function-like macro- 然后处理其parameter,即将
f(1,2)
替换成12
,即得到g(12)
- 继续往后走,得到
12
再看g(f(1,2))
:
g(f(1,2))
预处理先找到g
这个function-like macro- 然后替换parameter,发现替换后遇到
#
了,即得到#f(1,2)
- 再然后也没有然后了,将这个
#f(1,2)
变成了字符串f(1,2)
了,因为预处理遇到#
不会继续了。
宏替换除了以上这条规则,还有很多,有十几条之多,我之前在《基于C99规范,最全C语言预处理知识总结》有讲解,欢迎查阅。
问题延伸
顺着这个问题例子,来做个实验:
#define ABC "abc"
#define _STR(x) #x
#define STR(x) _STR(x)
char * pc1 = _STR(ABC);
char * pc2 = STR(ABC);
printf("pc1:%s\\n",pc1);
printf("pc2:%s\\n",pc2);
会输出什么结果?
其实不难理解,按照上面的套路可以得到(实际输出的结果):
pc1:ABC
pc2:"abc"
但是,注意啊,这个"abc"
是把双引号""
都打印出来的哦。
或者,可以通过gcc -E
来看看预编译后的结果:
# 3 "macro.c"
int main(void)
{
# 14 "macro.c"
char * pc1 = "ABC";
char * pc2 = "\\"abc\\"";
# ... ...
别着急疑惑,继续往下看(在上面例子基础上加点料,看完你会更疑惑):
char * pc3 = STR(_STR(ABC));
char * pc4 = STR(STR(ABC));
printf("pc3:%s\\n",pc3);
printf("pc4:%s\\n",pc4);
你觉得它会输出什么结果?
答案是:
pc3:"ABC"
pc4:"\\"abc\\""
不仅""
出来了,连\\
都输出来了。
哈哈,你品你细品!
关注公众号,获得更多精品推送。
以上是关于C语言宏定义中的迷惑行为的主要内容,如果未能解决你的问题,请参考以下文章