当前位置: C语言 -- 基础 -- 预处理指令

预处理指令(九)

5、重新扫描与进一步替换

在替换列表中所有形参都已替换,并且#运算符(字符串化操作)和##运算符(标记连接操作)预处理完成后,所有占位标记将被删除。得到的结果预处理标记序列与源文件中的所有后续预处理标记一起重新扫描,以查找需要替换的宏。

如果在对替换列表的扫描中(不包括其它源文件预处理标记。)发现正在被替换的宏名,该宏不会被替换。此外如果在嵌套的替换过程中遇到了正在被替换的宏名,该宏也不会被替换;未替换的宏不再进一步替换。

#define X Y
#define Y X

//第一步:将宏X替换为Y,宏X处于正在被替换的状态。
//第二步:将宏Y替换为X,
//因为宏X处于正在被替换的状态,所以X不会再被替换。
X;  //宏X会替换成X。

该规则确保了每个宏在特定的上下文中只被处理一次,相同宏在不同的上下文中仍可能被展开。


最终生成的、经过完全宏替换的预处理标记序列,即使其形式类似预处理指令,也不会作为预处理指令处理;但序列中的Pragma一元运算符表达式除外。

#define MACRO include <stdio.h>
# MACRO	//编译报错,error: invalid preprocessing directive #MACRO。

_Pragma( "GCC dependency \"a.c\"" )   //等价于预处理指令#pragma GCC dependency "a.c"。

该规则使宏展开结果中的类指令文本作为普通标记处理,只有_Pragma运算符在宏展开后仍保持特殊功能;这样可以防止宏展开意外生成预处理指令,从而提高程序的可预测性。


有些情况下替换是否嵌套并不清晰。

#define f(x) x*g
#define g(x) f(x)

//宏f(3)(5)可能展开为3*f(5);也可能展开为3*5*g。
//展开步骤:
//第一步:f(3)(5)展开为3*g(5);
//第二步:3*g(5)展开为3*f(5);
//如果替换是嵌套的,宏f处于正在被替换的状态,3*f(5)中的f(5)不会被替换。
//如果替换不是嵌套的,3*f(5)展开为3*5*g。
f(3)(5);

f(3)(5)使用GCC编译器(版本号:GCC 13.4.0。)展开结果为3*5*g;但严格符合ISO标准的程序不应依赖此类未指定行为。


6、宏定义的作用域

宏定义的作用域不受代码结构的影响,会持续到遇到对应的#undef指令;如果未遇到对应的#undef指令,会持续到预处理编译单元结束。

#define ZERO 0
#define ONE 1
...

#undef ZERO //宏ZERO作用域到这里结束。

//宏ONE作用域到预处理编译单元结束。

宏定义在编译阶段步骤4后不具有任何意义。


#undef预处理指令具有以下语法格式:

#undef identifier new-line

其中undef是指令名;identifier是表示宏名的标识符;new-line是换行符。


#undef预处理指令使指定的标识符不再定义为宏名。如果指定的标识符当前未被定义为宏名,该指令将被忽略。

#define SIZE 0
#undef SIZE
#define SIZE 1	//合法。

#undef	ZERO 	//ZERO未被定义为宏名,该指令将被忽略。

#define DATA 0
#define DATA 1	//替换列表不同,重定义非法。