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

预处理指令(六)

五、宏替换

紧跟在指令名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))	//非法。