宏中的#和##
#include #define f(a,b) a##b #define g(a) #a #define h(a) g(a) int main() { printf("%s\n",h(f(1,2))); printf("%s\n",g(f(1,2))); return 0; }
只需通过查看程序,“可能”期望输出,对于两个printf语句都是相同的。 但是在运行程序时,你会得到它:
bash$ ./a.out 12 f(1,2) bash$
为什么会这样?
因为这是预处理器的工作方式。
单个’#’将从给定的参数创建一个字符串,无论该参数包含什么,而double’##’将通过连接参数创建一个新的标记。
如果想要更好地理解宏的评估方式,请尝试查看预处理的输出(例如使用gcc -E
)。
除非是#
或##
的操作数,否则在类函数宏中出现参数会在替换它之前进行扩展,并重新扫描整体以进一步扩展。 因为g
的参数是 #
的操作数,所以参数不会扩展,而是立即进行字符串化( "f(1,2)"
)。 因为h
的参数不是 ##
的操作数,所以首先扩展参数( 12
),然后替换( g(12)
),然后重新扫描并进一步扩展( "12"
)。
以下是您问题的一些相关概念:
争论预言 :
宏参数在被替换为宏体之前是完全宏扩展的,除非它们被字符串化或粘贴到其他标记。 替换后,将再次扫描整个宏体(包括替换参数),以便扩展宏。 结果是参数被扫描两次以扩展其中的宏调用。
字串
当宏参数与前导’#’一起使用时,预处理器将其替换为实际参数的文字文本,并转换为字符串常量 。
令牌粘贴/令牌连接 :
在扩展宏的同时将两个令牌合并为一个通常很有用。 这称为令牌粘贴或令牌连接 。 ‘##’预处理运算符执行标记粘贴。 扩展宏时,每个’##’运算符两侧的两个标记组合成一个标记,然后在宏扩展中替换’##’和两个原始标记。
所以你的场景的详细过程是这样的:
h(f(1,2)) -> h(12) // f(1,2) pre-expanded since there's no # or ## in macro h -> g(12) // h expanded to g 12 // g expanded g(f(1,2)) -> "f(1,2)" //f(1,2) is literally strigified because of the `#` in macro g. f(1,2) is NOT expanded at all.