预处理指令(一)
一、概述
实现能够有条件地处理或者跳过源文件的某些部分、包含其它源文件以及替换宏,这称为预处理(preprocessing)。从概念上讲预处理发生在编译单元编译之前。预处理指令(preprocessing directive)由预处理标记(preprocessing tokens)序列构成,预处理标记应满足以下要求:
-- 序列的第一个标记是#预处理标记,该标记在编译阶段步骤4开始时必须是源文件的第一个字符(前面可以有空格,但不能有换行。),或者必须跟在包含至少一个换行符的空白序列之后。
-- 序列中的最后一个标记是序列中第一个标记后的第一个换行符。
-- 换行符会终止预处理指令,即使换行符出现在类似函数式宏(function-like macro)的调用过程中。
#include <stdio.h> //预处理指令。 #define N 10 //预处理指令。
文本行(即非指令行)不能以#预处理标记开头;非指令不能以任何指令名(directive names)开头。
(注:ISO/IEC 9899:2024标准中指令名包括:if、ifdef、ifndef、elif、elifdef、elifndef、else、endif、include、embed、define、undef、line、error、warning、pragma。)
ISO/IEC 9899:2024标准对预处理指令作了一些限制:
-- 预处理指令中(从#开始到换行符结束),预处理标记间只能出现两种空格字符(white-space characters):空格符和水平制表符;空格符包括编译阶段步骤3中替换注释或者其它空格字符的空格。
(注:C语言中标准空格字符包括空格符(' ')、换页符('\f')、换行符('\n')、回车符('\r')、水平制表符('\t')和垂直制表符('\v')。)
-- 预处理器形参只能是以下两种情况之一:预处理器标准形参或者实现定义的预处理器前缀形参。
#embed "gch.bin" limit(8) //limit是预处理器标准形参。 #embed "gch.bin" gnu::offset(8) //gnu::offset是预处理器前缀形参,用于GCC编译器。
预处理器标准形参(preprocessor standard parameter)由ISO标准定义;预处理器前缀形参(preprocessor prefixed parameter)由实现定义。
-- 无法识别的预处理器前缀形参是约束违规,但如果在has_embed表达式内除外。
//如果gnu::offsets预处理器前缀形参不存在, //表达式__has_embed( "gch.bin" gnu::offsets(8) )值会评估为0; //但这里预处理器前缀形参无法识别并不会约束违规。 #if __has_embed( "gch.bin" gnu::offsets(8) ) //等价于#if 0。
作为预处理器形参使用时,除了拼写差异外,预处理器标准形参(标识符格式为pp_param)和格式为__pp_param__的标识符应表现相同。
//以下两条预处理指令是等价的。 #embed "gch.bin" limit(8) #embed "gch.bin" __limit__(8)
当处于条件编译被跳过的代码块时(即处于条件编译不被编译的代码块时),预处理指令的语法限制会被放宽,允许在指令名和换行符之间出现任何预处理标记序列。
#if 0 //#include abcd不是合法的源文件包含指令; //但该代码会被跳过,不会被编译; //因此整个代码块仍然是合法的。 #include abcd #endif
除非另有明确说明,否则预处理指令中的预处理标记不会进行宏扩展(macro expansion)。
#define WHITESPACE WHITESPACE #define SIZE 100
第二行预处理标记序列不构成预处理指令,因为在编译阶段步骤4开始时它不是以#开头,即使宏WHITESPACE替换后该行会以#开头。执行非指令形式的预处理指令将导致未定义行为。