预处理指令(六)
五、宏替换
紧跟在指令名define后的标识符称为宏名(macro name)。
#define N 10 //N是宏名。 #define SUM(x, y) ((x) + (y)) //SUM是宏名。
所有宏名共享同一命名空间;这意味着无论是对象式宏(object-like macro),还是函数式宏(function-like macro),不能具有相同的宏名。
//因为宏名相同,编译时会报错。 #define SUM 10 #define SUM(x, y) ((x) + (y))
对象式宏的定义具有以下语法格式:
#define identifier replacement-list new-line
其中define是指令名;identifier是标识符,即宏名;replacement-list是替换列表;new-line是换行符。
该指令定义了一个对象式宏,该宏名的每个后续实例会被替换列表替换。该替换列表会被重新扫描以查找更多宏名。
#define TWO 2 #define FOUR 4 #define SUM TWO + FOUR ... int arr[SUM]; //等价于int arr[6];。
函数式宏的定义具有以下两种语法格式:
#define identifier lparen identifier-listopt ) replacement-list new-line #define identifier lparen identifier-listopt, ... ) replacement-list new-line
其中define是指令名;identifier是标识符,即宏名;lparen是前面没有空白的(字符;identifier-list是标识符列表,表示命名形参列表;...表示可变形参;replacement-list是替换列表;new-line是换行符。
#define HELLO() puts("hello world") #define SUM(x, y) ((x) + (y)) #define PRINT(format, ...) printf(format, ##__VA_ARGS__) #define FUNC(...) func(0 __VA_OPT__(,) __VA_ARGS__)
上述两种语法格式的指令均可定义函数式宏,函数式宏的使用类似于函数调用。函数式宏的形参由可选的标识符列表指定,这些标识符的作用域从其在列表中的声明开始,直至终止#define预处理指令的换行符为止。函数式宏名的每个后续实例,如果其后紧跟着一个(作为下一个预处理标记,那么就引入一个预处理标记序列,该序列会被定义中的替换列表所替换。这个被替换的预处理标记序列由匹配的)预处理标记终止,在此过程中会跳过中间成对匹配的( )预处理标记。在构成函数式宏调用的预处理标记序列中,换行符视为普通空格字符(white-space character)。
最外层匹配的( )符号界定的预处理标记序列构成了函数式宏的实参列表。实参列表中的实参使用,符号分隔,但内层( )中的,符号并不用于分隔实参。如果实参列表中存在本应作为预处理指令的预处理标记序列,其行为是未定义的。
(注:非指令是一种预处理指令,非指令不会被执行;如果执行非指令,将导致未定义行为。)
如果宏定义的标识符列表中存在...符号,则后续实参(如果存在。)包括分隔实参的逗号预处理标记合并为可变实参(variable arguments)。合并后的实参数量应满足:合并后实参总数应比宏定义中的命名形参总数多一。如果实参总数与宏定义中的命名形参总数相等,则宏调用行为像在实参列表末尾附加了一个,标记,从而形成不含预处理标记的可变实参。
无论是对象式宏,还是函数式宏,位于替换列表前后的所有空白字符(white-space characters)都不是替换列表的组成部分。
宏替换时,字符常量和字符串字面量都是预处理标记,而不是可能包含类似标识符子序列的序列,因此预处理器不会扫描字符常量和字符串字面量查找宏名或者形参。
#define N 10 #define CHARACTER 'N' #define STRING "N" ... printf("%d\n", N); //输出10。 printf("%c\n", CHARACTER); //输出N。 printf("%s\n", STRING); //输出N。
如果在预处理指令开始的位置,出现一个#预处理标记后跟一个标识符,该标识符不会进行宏替换。
#define INCLUDE include #INCLUDE <stdio.h> //INCLUDE不会发生宏替换,因此不会包含<stdio.h>头文件。
ISO/IEC 9899:2024标准对宏替换作了一些限制:
-- 当且仅当两个替换列表中的预处理标记数量相同、顺序相同、拼写相同、空白分隔(white-space separation)相同时,两个替换列表是相同的。
(注:这里所有空白分隔视为相同。)
-- 当前定义为对象式宏的标识符不能使用#define预处理指令重新定义,除非同时满足以下条件:① 重新定义是对象式宏定义;② 两次定义的替换列表完全相同。
#define N 10 #define N 10 //合法。 #define N 5 //非法,替换列表不同。
-- 当前定义为函数式宏的标识符不能使用#define预处理指令重新定义,除非同时满足以下条件:① 重新定义是函数式宏定义;② 两次定义的形参数量和拼写完全相同;③ 两次定义的替换列表完全相同。
#define SUM(x, y) ((x) + (y)) #define SUM(x, y) ((x) + (y)) //合法。 #define SUM(x, z) ((x) + (z)) //非法,形参拼写不同。 #define SUM(x, y) (x + y) //非法,替换列表不同。
-- 对象式宏定义时,标识符和替换列表之间应存在空白(white space)。
#define N 10 //合法。 #define M10 //未定义宏M。
-- 如果宏定义中的标识符列表(即形参列表。)不以...结尾,函数式宏调用时实参数(包括那些不包含预处理标记的实参。)应等于宏定义时的形参数;如果宏定义中的标识符列表(即形参列表。)以...结尾,函数式宏调用时实参数应至少等于宏定义时的形参数(不包括...。)。
#define STRINGIZE(x) # x #define TEMP(x) STRINGIZE(x) #define JOIN(x, y) TEMP(x ## y) #define PRINT(format, ...) printf(format, ##__VA_ARGS__) ... printf("%s\n", JOIN(x, y)); //实参数等于形参数。 printf("%s\n", JOIN(x,)); //第二个实参是不包含预处理标记的实参。 printf("%s\n", JOIN(, y)); //第一个实参是不包含预处理标记的实参。 PRINT("Hello\n"); //实参等于形参。 PRINT("%s%s", "Hello ", "World!\n"); //实参多于形参。
-- 标识符__VA_ARGS__和__VA_OPT__只能出现在形参使用...的函数式宏的替换列表中。
#define PRINT(format, ...) printf(format, ##__VA_ARGS__) #define FUNC(...) func(0 __VA_OPT__(,) __VA_ARGS__)
-- 函数式宏中的形参标识符应在其作用域范围内唯一声明。
#define SUM(x, x) ((x) + (x)) //非法。