6.10.2 条件包含
语法
形式化语法
bnf
defined-macro-expression:
defined identifier
defined ( identifier )
h-preprocessing-token:
any preprocessing-token other than >
h-pp-tokens:
h-preprocessing-token
h-pp-tokens h-preprocessing-token
header-name-tokens:
string-literal
< h-pp-tokens >
has-include-expression:
__has_include ( header-name )
__has_include ( header-name-tokens )
has-embed-expression:
__has_embed ( header-name embed-parameter-sequenceopt )
__has_embed ( header-name-tokens pp-balanced-token-sequenceopt )
has-c-attribute-express:
__has_c_attribute ( pp-tokens )1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#if 与 #elif 统称为条件表达式包含预处理指令。条件表达式包含预处理指令,再加上 #ifdef、#ifndef、#elifdef 和 #elifndef,统称为条件包含预处理指令。
约束
- 控制条件包含的表达式必须是整数常量表达式,但有以下例外:
- 其中的标识符,包括在词法上与关键字相同的那些标识符,按本小节后文规定的方式解释。
- 该表达式可以包含零个或多个
defined宏表达式、has_include表达式、has_embed表达式和has_c_attribute表达式,且它们作为一元运算符表达式出现。 defined宏表达式在该标识符当前被定义为宏名时求值为1,否则求值为0。这里的“当前被定义”为:它要么是预定义宏,要么曾经由#define定义,并且之后没有被同名#undef取消。has_include和has_embed的第二种形式,只有在第一种形式不匹配时才会考虑;此时其中的预处理记号按普通文本的方式处理。- 每个
has_include表达式中括号里的头名或源文件,会像在#include指令中那样被搜索,但不会进一步进行宏展开。该形式必须满足#include指令的语法要求。搜索成功时,has_include求值为1;失败时求值为0。 - 每个
has_embed表达式中由头名预处理记号序列标识的资源,会像在#embed指令中那样被搜索,但不会进一步进行宏展开。该形式必须满足#embed指令的语法要求。它的值与以下预定义宏之一相同:
__STDC_EMBED_NOT_FOUND__:搜索失败,或者给出的某个嵌入参数不被实现支持;__STDC_EMBED_FOUND__:搜索成功,所有嵌入参数都受支持,且资源非空;__STDC_EMBED_EMPTY__:搜索成功,所有嵌入参数都受支持,但资源为空。
- 在
has_embed表达式里,无法识别的预处理器前缀参数不构成违反约束,而是按前述规则让表达式求值为0。 - 如果实现支持由把
pp-tokens解释为属性记号后得到的属性名,那么每个has_c_attribute表达式都会被替换为一个符合整数常量形式的非零pp-number;否则替换为0。这里的pp-tokens必须匹配属性记号的形式。 - 在所有宏替换都完成后,保留在控制表达式预处理记号序列中的每一个预处理记号,都必须符合一个词法记号的形式。
语义
#ifdef、#ifndef、#elifdef、#elifndef指令,以及defined条件包含运算符,必须把__has_include、__has_embed和__has_c_attribute视为已定义的宏名。- 标识符
__has_include、__has_embed和__has_c_attribute不得出现在本小节未提及的其他上下文中。 - 形如
c
#if constant-expression
#elif constant-expression1
2
2
的预处理指令,会检查控制常量表达式是否求值为非零。
- 在求值之前,控制常量表达式中的宏调用会像普通文本那样先做宏替换,但由
defined一元运算符修饰的宏名除外。 - 如果记号
defined是在这种替换过程中生成的,或者在宏替换之前使用defined一元运算符的形式不符合规定的两种写法之一,那么行为未定义。 - 在完成宏展开、
defined宏表达式、has_include表达式、has_embed表达式和has_c_attribute表达式的求值之后,所有剩余标识符中,除true之外的都替换成pp-number 0;true替换成pp-number 1;然后每个预处理记号再转换成词法记号。 - 这样得到的记号序列组成控制常量表达式,并按
6.6的规则求值。 - 为了这种记号转换和求值,所有有符号整数类型与所有无符号整数类型,都分别按
intmax_t与uintmax_t的表示来处理。 - 这也包括字符常量的解释,其中可能涉及把转义序列转换为执行字符集成员。
- 这种字符常量的数值是否与相同字符常量出现在普通表达式中时得到的值一致,是实现定义的。
- 单字符字符常量是否可能为负值,也是实现定义的。
- 形如
c
#ifdef identifier
#ifndef identifier
#elifdef identifier
#elifndef identifier1
2
3
4
2
3
4
的预处理指令,检查该标识符当前是否被定义为宏名,或者是否未被定义为宏名。它们分别等价于 #if defined identifier、#if !defined identifier、#elif defined identifier 和 #elif !defined identifier。
- 各个指令的条件按顺序检查。
- 如果某个条件求值为假,也就是
0,那么它所控制的组会被跳过:这时只处理足以识别该指令名称、并跟踪嵌套条件层级的那部分指令,其余预处理记号以及该组中的其他预处理记号都被忽略。 - 只有第一个求值为真的组会被处理;其后的组都会被跳过,并且它们的控制指令会按“位于被跳过组中”的方式处理。
- 如果所有条件都不为真,而存在
#else指令,那么处理#else所控制的组;如果没有#else,则一直到#endif的所有组都被跳过。
注
注 1
在实现中,INT_MAX 为 0x7FFF 且 UINT_MAX 为 0xFFFF 时,常量 0x8000 在 #if 表达式里是有符号且为正的;但在翻译阶段 7 中,它会是无符号的。
注 2
下面 #if 指令和 if 语句中的常量表达式,不保证得到相同的值:
c
#if 'z' - 'a' == 25
if ('z' - 'a' == 25)1
2
2
示例
示例 1
下面演示了只在头文件可用时才包含它的一种写法:
c
#if __has_include(<optional.h>)
# include <optional.h>
# define have_optional 1
#elif __has_include(<experimental/optional.h>)
# include <experimental/optional.h>
# define have_optional 1
# define have_experimental_optional 1
#endif
#ifndef have_optional
# define have_optional 0
#endif1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
示例 2
c
/* 为尚未实现此特性的编译器提供后备定义。 */
#ifndef __has_c_attribute
#define __has_c_attribute(x) 0
#endif /* __has_c_attribute */
#if __has_c_attribute(fallthrough)
/* 标准属性可用,使用它。 */
#define FALLTHROUGH [[fallthrough]]
#elif __has_c_attribute(vendor::fallthrough)
/* 厂商属性可用,使用它。 */
#define FALLTHROUGH [[vendor::fallthrough]]
#else
/* 后备实现。 */
#define FALLTHROUGH
#endif1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15