6.4.2 标识符
TIP
这一节不只是“名字能怎么写”,还规定了保留标识符、潜在保留标识符以及 __func__ 的语义。很多项目代码最容易踩的,就是保留标识符这一部分。
6.4.2.1 一般规定
标识符的语法由“起始字符”与“后续字符”组成:
- 首字符必须是
nondigit、XID_Start字符,或属于XID_Start类的通用字符名; - 后续字符可以是数字、
nondigit、XID_Continue字符,或属于XID_Continue类的通用字符名。
- 首字符必须是
nondigit包括下划线_以及英文字母a到z、A到Z;数字则是0到9。XID_Start和XID_Continue都由实现定义,但它们所对应的 ISO/IEC 10646 码点必须分别具有XID_Start与XID_Continue属性。标识符是“一个起始字符,后跟零个或多个后续字符”的序列,用来指代
6.2.1所说的一个或多个实体。小写字母与大写字母不同。标准对标识符最大长度不设专门上限。$(U+0024,DOLLAR SIGN)是否可以作为nondigit使用,由实现定义。标识符中的每个字符与通用字符名,都必须指向在 ISO/IEC 10646 中具有
XID_Continue属性的字符;首字符还必须具有XID_Start属性。一个标识符还必须符合 Normalization Form C(NFC)。在翻译阶段 4 中,“标识符”这个术语还包括那些在后续翻译阶段 7 会被识别为关键字的预处理记号;而在翻译阶段 7 中,如果一个预处理记号既可能转换为关键字,也可能转换为标识符,那么除属性记号外,它必须转换为关键字。
6.4.2.1 保留标识符
下列标识符被保留:
- 以双下划线
__开头的全部标识符; - 以下划线
_开头且后接大写字母的全部标识符; - 以上两类中,与关键字拼写完全相同者除外;
- 以单下划线
_开头的全部标识符,在文件作用域下的普通名字空间与标记名字空间中也被保留。
- 以双下划线
其他保留标识符见
7.1.3。如果程序在某个标识符被保留的上下文中声明或定义它,而又不属于
7.1.4允许的情形,则行为未定义。如果程序把保留标识符、或
6.7.13.2所述标准属性记号定义为宏名,或者使用#undef取消前述第一组保留标识符或标准属性记号的宏定义,则行为未定义。
WARNING
以 _Xxx 或 __xxx 命名自己的接口,不是“风格问题”,而是可能直接落入未定义行为。
6.4.2.1 潜在保留标识符
有些标识符属于潜在保留标识符。它们并不是天然保留,而是当实现提供了该标识符时才保留;同时,标准预期它们可能在未来版本中转为正式保留。
对于标准描述为“可选”的标识符:
- 如果它被定义为宏,则它是保留的;
- 否则,若其定义出现在第
1到第6章中,则它是保留的; - 否则,它只是潜在保留的。
推荐实践是:若程序以与实现不兼容的方式声明或定义某个潜在保留标识符,实现应给出诊断消息,以提示未来移植风险。
所谓“与实现兼容的使用”,是指:该名字本来就是实现提供的外部名,而程序用兼容类型再次声明同名对象或函数。
6.4.2.1 实现限度
实现可以限制标识符中“有意义的前导字符”个数。外部名(具有外部链接的标识符)的限制,可以比内部名更严格。
标识符中究竟多少前导字符算“有意义”,由实现定义。
只要两个标识符在有意义字符上不同,它们就是不同标识符;若它们只在无意义字符上不同,则行为未定义。
6.4.2.2 预定义标识符
- 对每个函数定义,翻译器都必须像是在函数体起始左花括号之后立即隐式插入了如下声明一样处理:
static const char __func__[] = "function-name";这里的 "function-name" 是词法上包围当前定义的函数名。
该名字的编码方式,应等同于:先把这条隐式声明写在源字符集里,再在翻译阶段 5 按规则转换到执行字符集。
因而在函数内部使用
__func__,可以得到当前函数名对应的字符串。
标准示例
#include <stdio.h>
void myfunc(void)
{
printf("%s\n", __func__);
}2
3
4
5
6
每次调用 myfunc 时,输出的就是:
myfunc