词法元素(二)
二、标识符
标识符(identifier)是由一个首字符(start character)和0个或者多个连续字符(continue characters)构成的序列,用以指定一个或者多个实体。构成标识符的字符可分为以下4类:
-- 数字字符:0~9;
-- 非数字字符:下划线字符(_)、大写拉丁字母(A~Z),小写拉丁字母(a~z);
-- XID_Continue类字符;
-- XID_Continue类通用字符名。
(注:XID_Continue类字符和XID_Continue类通用字符名是ISO/IEC 9899:2024标准新增的字符类别;分别对应ISO/IEC 9899:2018标准中的实现定义字符和通用字符名。)
XID_Continue字符是实现定义字符,在ISO/IEC 10646:2020标准中其对应码点(code point)具有XID_Continue属性。XID_Start字符是实现定义字符,在ISO/IEC 10646:2020标准中其对应码点(code point)具有XID_Start属性。XID_Start字符是XID_Continue字符的子集。字符类XID_Start属性和XID_Continue属性是UAX#44描述的派生核心属性。
实现可能允许在标识符中使用不属于基本源字符集的多字节字符,这些字符及其对应的通用字符名由实现定义。在链接器不接受扩展字符的系统中,可以使用通用字符名来构成有效的外部标识符,例如:可以使用某些未使用的字符或者字符序列对通用字符名中的u进行编码。
标识符中的每个字符和通用字符名应表示一个字符,其在ISO/IEC 10646:2020标准中编码具有XID_Continue属性。标识符的首字符(可以是通用字符名。)应表示一个字符,其在ISO/IEC 10646:2020标准中编码具有XID_Start属性。标识符应符合ISO/IEC 10646:2020标准规定的规范化格式C(Normalization Form C)。
1、C语言中标识符命名规则及其注意事项:
-- 标识符首字符应为非数字字符、XID_Start类字符或者XID_Start类通用字符名。
合法的标识符:
Day1 you_next geng
非法的标识符:
1day
非法原因:标识符1day以数字开头。
-- 标识符是区分大小写的。
标识符apple和Apple是两个不同的标识符。
-- 字符$能否用作非数字字符将由实现定义。
-- 标识符内不能存在空格。
空格字符不是XID_Continue字符。
例如:hello world这样的标识符是不允许的。
-- 标识符不能与C语言关键词同名。
这是因为在编译的第7阶段当预处理标记转换成标记时,如果一个预处理标记既可以转换成关键词,又可以转换成标识符,将转换成关键词(属性标记除外。)。
-- 标识符不能与保留标识符同名。
如果同名,可能导致编译错误或者链接错误,例如:自定义printf函数,编译时编译器会给出类似error: conflicting types for 'printf'的出错信息。
-- 标识符不宜太长。
ISO/IEC 9899:2024标准对标识符的最大长度未作明确限制,但具体实现中编译器能够识别的标识符的有效字符数(significant characters)是有限制的。根据ISO/IEC 9899:2024标准第5.3.5.2 Translation limits节,支持C标准的编译器对于内部标识符或者宏名至少能够识别前63个字符;对于外部标识符至少能够识别前31个字符。对于两个标识符,如果有效字符(significant character)存在差异,这两个标识符是不同标识符;如果只是非有效字符(nonsignificant characters)存在差异,其行为是未定义的。
2、预定义标识符(predefined identifiers)
ISO/IEC 9899:2024标准提供了一个预定义标识符__func__,编译器应隐式地声明该标识符,其声明如下所示:
static const char __func__[] = "function-name";
其中function-name是函数名。
在函数中使用__func__标识符,可以获取函数名,例如:
void func(void) { puts(__func__); }
调用func函数,将输出:
func
预定义标识符是为实现保留的标识符,如果显式地声明与预定义标识符同名的标识符,其行为是未定义的。
3、保留标识符(reserved identifiers)
保留标识符包括标准库头文件中声明或者定义的标识符,未来标准库中可能声明或者定义的标识符,始终保留用于某种用途或者用作文件作用域标识符的标识符,具体有以下6种情况:
-- 以两个下划线或者一个下划线和一个大写字母开始的标识符始终保留用于某种用途(与关键词相同的标识符除外。)。
-- 所有以下划线开头的标识符都被保留,以便在普通标识符和标记命名空间内作为文件作用域标识符使用。
-- 由具有外部定义的实现提供的所有潜在保留标识符(包括未来标准库头文件中列出的标识符。)都保留用于某种用途。实现不应提供潜在保留标识符的外部定义,除非该标识符保留用于具有外部链接的用途。当包含相关联标准头文件时,由实现(包括以宏形式。)提供的所有其它潜在保留标识符都保留用于某种用途。没有保留其它潜在保留标识符。
(注:潜在保留标识符(potentially reserved identifier)是指由提供标识符的实现保留,否则不会保留的标识符。 以下情况潜在保留标识符会转变成保留标识符:① 实现使用它,② 未来标准保留它;其它情况下编程人员可以使用它。)
-- 除非另有明确说明,否则在标准库头文件(包括未来标准库头文件)中定义的宏是保留标识符。
-- 在标准库头文件中列出的具有外部链接的标识符以及标识符errno始终保留为具有外部链接的标识符。(具有外部链接的保留标识符包括math_errhandling、setjmp、va_copy和va_end。)
-- 在标准库头文件中列出的具有文件作用域的标识符保留用作宏名或者相同命名空间内具有文件作用域的标识符。
如果程序声明或者定义了一个保留标识符,其行为是未定义的。
如果程序将保留标识符或者标准属性标记定义成宏名,或者使用#undef指令删除第1种情况中的宏定义或者标准属性标记,其行为是未定义的。