C11 & C++11 扩展和通用字符转义
C11 & C++11 Exended and Universal Character Escaping
上下文
C11 和 C++11 都支持源文件中的扩展字符,以及通用字符名称 (UCN),它允许只使用属于基本源字符集的字符来输入不属于基本源字符集的字符。
C++11 还定义了编译的几个翻译阶段。特别是,扩展字符在翻译的第一阶段被规范化为 UCN,如下所述:
§ C++11 2.2p1.1:
Physical source file characters are mapped, in an
implementation-defined manner, to the basic source character set
(introducing new-line characters for end-of-line indicators) if
necessary. The set of physical source file characters accepted is
implementation-defined. Trigraph sequences (2.4) are replaced by
corresponding single-character internal representations. Any source
file character not in the basic source character set (2.3) is replaced
by the universal-character-name that designates that character. (An
implementation may use any internal encoding, so long as an actual
extended character encountered in the source file, and the same
extended character expressed in the source file as a
universal-character-name (i.e., using the \uXXXX notation), are
handled equivalently except where this replacement is reverted in a
raw string literal.)
问题
因此,我的问题是:
Does a Standard-conforming compilation of the program
#include <stdio.h>
int main(void){
printf("\é\n");
printf("\u00e9\n");
return 0;
}
fail, compile and print
é
é
or compile and print
\u00e9
\u00e9
, when run?
知情的个人意见
我认为答案是它成功编译并打印 \u00e9
,因为根据上面的 §2.2p1.1,我们有
实现可以使用任何内部编码,只要在源文件中遇到实际的扩展字符,并且相同的扩展在源文件中表示为通用字符名称(即使用 \uXXXX 表示法)的字符,被等效地处理 除非此替换在原始字符串文字中恢复。,并且我们不在原始字符串文字中。
那么
- 在第 1 阶段,
printf("\é\n");
映射到 printf("\u00e9\n");
。
- 在第 3 阶段,源文件被分解为预处理标记 (§2.2p1.3),其中 string-literal
"\u00e9\n"
是一个。
- 在第 5 阶段,字符文字或字符串文字中的每个源字符集成员,以及字符文字或非原始字符串中的每个转义序列和通用字符名称文字,转换为执行字符集 (§2.2p1.5) 的相应成员。因此,根据最大蒙克原则,
\
映射到 \
,片段 u00e9
未被识别为 UCN,因此按原样打印。
实验
不幸的是,现存的编译器不同意我的观点。我已经用 GCC 4.8.2 和 Clang 3.5 进行了测试,这是他们给我的:
海湾合作委员会 4.8.2
$ g++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn
ucn.cpp: In function 'int main()':
ucn.cpp:4:9: warning: unknown escape sequence: '3' [enabled by default]
printf("\é\n");
^
$ ./ucn
é
\u00e9
叮当声 3.5
$ clang++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn
ucn.cpp:4:10: warning: unknown escape sequence '\xFFFFFFC3' [-Wunknown-escape-sequence]
printf("\é\n");
^
ucn.cpp:4:12: warning: illegal character encoding in string literal [-Winvalid-source-encoding]
printf("\é\n");
^
2 warnings generated.
$ ./ucn
é
\u00e9
我已经双重和三次检查 é
字符显示为 C3 A9
使用 hexdump -C ucn.cpp
,与预期的 UTF-8 编码一致。此外,我还验证了普通的 printf("é\n");
或 printf("\u00e9\n");
可以完美地工作,所以这不是测试的编译器无法读取 UTF-8 源文件的问题。
谁说得对?
您似乎很困惑,认为 \u00e9
是 UCN -- 它不是。 UCN 都以 \u
开头,在您的情况下,您有一个提取反斜杠来转义这个初始反斜杠。所以\u00e9
是6个字符的序列:\
、u
、0
、0
、e
、9
.
编辑
In Phase 1, printf("\é\n"); maps to printf("\u00e9\n");.
这就是你出错的地方——第 1 阶段将输入字符转换为源字符,因此 printf("\é\n");
映射到 p
r
i
n
t
f
(
"
\
é
\
n
"
)
;
,与p
r
i
n
t
f
(
"
\
\u00e9
\
n
"
)
;
, 但那和printf("\u00e9\n");
映射到因为后者中的双反斜杠。由于对双反斜杠的特殊处理,源代码中无法在反斜杠后跟 UCN。
'é' 不是字符串文字中反斜杠转义的有效字符,因此反斜杠后跟 'é' 作为文字源字符或 UCN 应该会产生编译器诊断和未定义的行为。
但是请注意,"\u00e9"
不是以反斜杠开头的 UCN,并且不可能在反斜杠后跟一个字符串或字符文字中写入任何基本源字符序列联合国教科文组织。因此 "\é"
和 "\u00e9"
不需要表现相同:"\u00e9"
的行为可以完美定义,而 "\é"
的行为未定义。
如果我们要设置一些允许反斜杠转义 UCN 的语法,例如 "\«\u00e9»"
,那么这将具有未定义的行为,例如 "\é"
。
- In Phase 1,
printf("\é\n");
maps to printf("\u00e9\n");
.
é
到 UCN 的第一阶段转换不能创建非 UCN,例如 "\u00e9"
。
编译器是对的,但并没有用完美的诊断消息专门处理这种情况。理想情况下你会得到:
$ clang++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn
ucn.cpp:4:10: warning: unknown escape sequence '\é' [-Wunknown-escape-sequence]
printf("\é\n");
^
1 warnings generated.
$ ./ucn
é
\u00e9
两个编译器都指定它们在存在未知转义序列时的行为是用转义的字符替换转义序列,因此 "\é"
将被视为 "é"
并且整个程序应解释为:
#include <stdio.h>
int main(void){
printf("é\n");
printf("\u00e9\n");
return 0;
}
两个编译器确实碰巧得到这种行为,部分是偶然的,但也部分是因为按照他们的方式处理无法识别的转义序列的策略是一个明智的选择:即使他们只将无法识别的转义序列视为反斜杠后跟字节 0xC3,他们删除反斜杠并将 0xC3 保留在原位,这意味着 UTF-8 序列保持不变以供以后处理。
上下文
C11 和 C++11 都支持源文件中的扩展字符,以及通用字符名称 (UCN),它允许只使用属于基本源字符集的字符来输入不属于基本源字符集的字符。
C++11 还定义了编译的几个翻译阶段。特别是,扩展字符在翻译的第一阶段被规范化为 UCN,如下所述:
§ C++11 2.2p1.1:
Physical source file characters are mapped, in an implementation-defined manner, to the basic source character set (introducing new-line characters for end-of-line indicators) if necessary. The set of physical source file characters accepted is implementation-defined. Trigraph sequences (2.4) are replaced by corresponding single-character internal representations. Any source file character not in the basic source character set (2.3) is replaced by the universal-character-name that designates that character. (An implementation may use any internal encoding, so long as an actual extended character encountered in the source file, and the same extended character expressed in the source file as a universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this replacement is reverted in a raw string literal.)
问题
因此,我的问题是:
Does a Standard-conforming compilation of the program
#include <stdio.h> int main(void){ printf("\é\n"); printf("\u00e9\n"); return 0; }
fail, compile and print
é é
or compile and print
\u00e9 \u00e9
, when run?
知情的个人意见
我认为答案是它成功编译并打印 \u00e9
,因为根据上面的 §2.2p1.1,我们有
实现可以使用任何内部编码,只要在源文件中遇到实际的扩展字符,并且相同的扩展在源文件中表示为通用字符名称(即使用 \uXXXX 表示法)的字符,被等效地处理 除非此替换在原始字符串文字中恢复。,并且我们不在原始字符串文字中。
那么
- 在第 1 阶段,
printf("\é\n");
映射到printf("\u00e9\n");
。 - 在第 3 阶段,源文件被分解为预处理标记 (§2.2p1.3),其中 string-literal
"\u00e9\n"
是一个。 - 在第 5 阶段,字符文字或字符串文字中的每个源字符集成员,以及字符文字或非原始字符串中的每个转义序列和通用字符名称文字,转换为执行字符集 (§2.2p1.5) 的相应成员。因此,根据最大蒙克原则,
\
映射到\
,片段u00e9
未被识别为 UCN,因此按原样打印。
实验
不幸的是,现存的编译器不同意我的观点。我已经用 GCC 4.8.2 和 Clang 3.5 进行了测试,这是他们给我的:
海湾合作委员会 4.8.2
$ g++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn ucn.cpp: In function 'int main()': ucn.cpp:4:9: warning: unknown escape sequence: '3' [enabled by default] printf("\é\n"); ^ $ ./ucn é \u00e9
叮当声 3.5
$ clang++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn ucn.cpp:4:10: warning: unknown escape sequence '\xFFFFFFC3' [-Wunknown-escape-sequence] printf("\é\n"); ^ ucn.cpp:4:12: warning: illegal character encoding in string literal [-Winvalid-source-encoding] printf("\é\n"); ^ 2 warnings generated. $ ./ucn é \u00e9
我已经双重和三次检查 é
字符显示为 C3 A9
使用 hexdump -C ucn.cpp
,与预期的 UTF-8 编码一致。此外,我还验证了普通的 printf("é\n");
或 printf("\u00e9\n");
可以完美地工作,所以这不是测试的编译器无法读取 UTF-8 源文件的问题。
谁说得对?
您似乎很困惑,认为 \u00e9
是 UCN -- 它不是。 UCN 都以 \u
开头,在您的情况下,您有一个提取反斜杠来转义这个初始反斜杠。所以\u00e9
是6个字符的序列:\
、u
、0
、0
、e
、9
.
编辑
In Phase 1, printf("\é\n"); maps to printf("\u00e9\n");.
这就是你出错的地方——第 1 阶段将输入字符转换为源字符,因此 printf("\é\n");
映射到 p
r
i
n
t
f
(
"
\
é
\
n
"
)
;
,与p
r
i
n
t
f
(
"
\
\u00e9
\
n
"
)
;
, 但那和printf("\u00e9\n");
映射到因为后者中的双反斜杠。由于对双反斜杠的特殊处理,源代码中无法在反斜杠后跟 UCN。
'é' 不是字符串文字中反斜杠转义的有效字符,因此反斜杠后跟 'é' 作为文字源字符或 UCN 应该会产生编译器诊断和未定义的行为。
但是请注意,"\u00e9"
不是以反斜杠开头的 UCN,并且不可能在反斜杠后跟一个字符串或字符文字中写入任何基本源字符序列联合国教科文组织。因此 "\é"
和 "\u00e9"
不需要表现相同:"\u00e9"
的行为可以完美定义,而 "\é"
的行为未定义。
如果我们要设置一些允许反斜杠转义 UCN 的语法,例如 "\«\u00e9»"
,那么这将具有未定义的行为,例如 "\é"
。
- In Phase 1,
printf("\é\n");
maps toprintf("\u00e9\n");
.
é
到 UCN 的第一阶段转换不能创建非 UCN,例如 "\u00e9"
。
编译器是对的,但并没有用完美的诊断消息专门处理这种情况。理想情况下你会得到:
$ clang++ -std=c++11 -Wall -Wextra ucn.cpp -o ucn
ucn.cpp:4:10: warning: unknown escape sequence '\é' [-Wunknown-escape-sequence]
printf("\é\n");
^
1 warnings generated.
$ ./ucn
é
\u00e9
两个编译器都指定它们在存在未知转义序列时的行为是用转义的字符替换转义序列,因此 "\é"
将被视为 "é"
并且整个程序应解释为:
#include <stdio.h>
int main(void){
printf("é\n");
printf("\u00e9\n");
return 0;
}
两个编译器确实碰巧得到这种行为,部分是偶然的,但也部分是因为按照他们的方式处理无法识别的转义序列的策略是一个明智的选择:即使他们只将无法识别的转义序列视为反斜杠后跟字节 0xC3,他们删除反斜杠并将 0xC3 保留在原位,这意味着 UTF-8 序列保持不变以供以后处理。