上下文
C11和C++11都支持源文件中的扩展字符,以及通用字符名称(UCN),它允许用户仅使用基本源字符集中的字符输入基本源字符集中未包含的字符。
C++11还定义了编译的几个翻译阶段。特别地,扩展字符在翻译的第一阶段被规范化为UCN,如下所述:
§ C++11 2.2p1.1:
如果需要,物理源文件字符以实现定义的方式Map到基本源字符集(为行尾指示符引入换行符)。接受的物理源文件字符集是实现定义的。三字母序列(2.4)被相应的单字符内部表示所取代。任何不在基本源字符集(2.3)中的源文件字符都被指定该字符的通用字符名替换。(实现可以使用任何内部编码,只要在源文件中遇到的实际扩展字符,以及在源文件中表示为通用字符名称的相同扩展字符(即,使用\uXXXX符号),被等效地处理,除非在原始字符串文字中恢复此替换。
提问
符合标准的程序编译
#include <stdio.h>
int main(void){
printf("\é\n");
printf("\\u00e9\n");
return 0;
}
失败,编译并打印
é
é
或编译打印
\u00e9
\u00e9
,何时运行?
知情个人意见
我认为答案是它成功地编译并打印了\u00e9
,因为根据上面的§2.2p1.1,我们有
- 实现可以使用任何内部编码,只要在源文件中遇到的实际扩展字符,以及在源文件中表示为通用字符名称的相同扩展字符(即,使用\uXXXX表示法),被处理等效,除非这种替换在原始字符串文字中恢复。*,并且我们不在原始字符串文字中。
然后得出结论
- 在阶段1中,
printf("\é\n");
Map到printf("\\u00e9\n");
。 - 在阶段3中,* 源文件被分解为预处理标记 *(§2.2p1.3),其中 string-literal
"\\u00e9\n"
是其中之一。 - 在阶段5中,* 字符文字或字符串文字中的每个源字符集成员,以及字符文字或非原始字符串文字中的每个转义序列和通用字符名称,都被转换为执行字符集的相应成员 *(§2.2p1.5)。因此,根据最大munch原则,
\\
Map到\
,并且片段u00e9
不被识别为UCN,因此按原样打印。
实验
不幸的是,现存的编译器不同意我的观点。我已经测试了GCC 4.8.2和Clang 3.5,下面是他们给我的:
- GCC 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: '\303' [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
我使用hexdump -C ucn.cpp
进行了两次和三次检查,以确保é
字符显示为C3 A9
,与预期的UTF-8编码一致。此外,我还验证了普通的printf("é\n");
或printf("\u00e9\n");
可以完美地工作,所以这不是测试的编译器无法读取UTF-8源文件的问题。
谁是对的?***
2条答案
按热度按时间dxxyhpgq1#
'é'不是字符串文字中反斜杠转义的有效字符,因此反斜杠后跟'é'作为文字源字符或UCN应产生编译器诊断和未定义行为。
但是,请注意,
"\\u00e9"
不是前面有反斜杠的UCN,并且不可能在反斜杠后面有UCN的字符串或字符字面量中写入任何基本源字符序列。因此,"\é"
和"\\u00e9"
不需要表现相同:"\\u00e9"
的行为可以很好地定义,而"\é"
的行为是未定义的。如果我们设定一些允许反斜杠转义UCN的语法,比如
"\«\u00e9»"
,那么它将具有像"\é"
这样的未定义行为。printf("\é\n");
Map到printf("\\u00e9\n");
。é
到UCN的第一阶段转换无法创建非UCN,例如"\\u00e9"
。编译器是正确的,但没有专门用完美的诊断消息来处理这种情况。理想情况下,你会得到:
这两个编译器都指定,在出现未知转义序列时,它们的行为是用转义的字符替换转义序列,因此
"\é"
将被视为"é"
,整个程序应解释为:两个编译器都碰巧得到了这种行为,部分是偶然的,但也部分是因为处理无法识别的转义序列的策略是一个明智的选择:即使它们只看到无法识别的转义序列,即反斜杠后跟字节0xC 3,它们也会删除反斜杠并保留0xC 3,这意味着UTF-8序列保持不变以供以后处理。
z2acfund2#
您似乎感到困惑,认为
\\u00e9
是UCN --它不是。UCN都以\u
开始,在您的例子中,您有一个提取反斜杠,它转义了这个初始反斜杠。所以\\u00e9
是6个字符的序列:\
、u
、0
、0
、e
、9
。编辑
在阶段1中,printf(“\é\n”); printf(“”);.
这就是错误所在--阶段1将输入字符转换为源字符,因此
printf("\é\n");
Map为p
r
i
n
t
f
(
"
\
é
\
n
"
)
;
,其与p
r
i
n
t
f
(
"
\
\u00e9
\
n
"
)
;
相同,但这与printf("\\u00e9\n");
Map到的不一样,因为后者中有双反斜杠。由于对双反斜杠的特殊处理,在源代码中无法在反斜杠后面加上UCN。