C11和C++ 11 Exended和Universal Character Escaping

Iwi*_*ist 8 c c++ language-lawyer c++11 c11

上下文

C11和C++ 11都支持源文件中的扩展字符,以及通用字符名称(UCN),它们允许用户只输入字符而不是基本源字符集中的字符.

C++ 11还定义了编译的几个翻译阶段.特别是,扩展字符在翻译的第一阶段被归一化为UCN,如下所述:

§C++ 11 2.2p1.1:

如果需要,物理源文件字符以实现定义的方式映射到基本源字符集(引入行尾指示符的换行符).接受的物理源文件字符集是实现定义的.Trigraph序列(2.4)由相应的单字符内部表示代替.不在基本源字符集(2.3)中的任何源文件字符将替换为指定该字符的通用字符名称.(实现可以使用任何内部编码,只要在源文件中遇到实际扩展字符,并且在源文件中表示为与通用字符名称相同的扩展字符(即,使用\ uXXXX表示法),处理等效,除非在原始字符串文字中还原此替换.)


因此,我的问题是:

是否符合标准的程序汇编

#include <stdio.h>

int main(void){
        printf("\é\n");
        printf("\\u00e9\n");
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

失败,编译和打印

é
é
Run Code Online (Sandbox Code Playgroud)

或编译和打印

\u00e9
\u00e9
Run Code Online (Sandbox Code Playgroud)

,什么时候跑?


知情的个人意见

我的论点是答案是它成功编译和打印\u00e9,因为根据上面的§2.2p1.1,我们有

实现可以使用任何内部编码,只要在源文件中遇到实际的扩展字符,并且在源文件中表示为与通用字符名称相同的扩展字符(即,使用\ uXXXX表示法),等效,除非在原始字符串文字中还原此替换.,我们不是原始的字符串文字.

然后是这样的

  • 在阶段1中,printf("\é\n");映射到printf("\\u00e9\n");.
  • 在阶段3中,源文件被分解为预处理令牌(§2.2p1.3),其中string-literal "\\u00e9\n"为1.
  • 在阶段5中,字符文字或字符串文字中的每个源字符集成员,以及字符文字或非原始字符串文字中的每个转义序列和通用字符名称,都将转换为相应的成员.执行字符集(§2.2p1.5).因此,通过最大蒙克原理,\\映射到\,并且片段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
    
    Run Code Online (Sandbox Code Playgroud)
  • Clang 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
    
    Run Code Online (Sandbox Code Playgroud)

我有双重和三重检查é字符显示为C3 A9使用hexdump -C ucn.cpp,与预期的UTF-8编码一致.我还验证了一个普通的printf("é\n");printf("\u00e9\n");完美的工作,所以这不是被测试的编译器无法读取UTF-8源文件的问题.

谁是对的?

bam*_*s53 2

\'\xc3\xa9\' 不是字符串文字中反斜杠转义的有效字符,因此反斜杠后跟 \'\xc3\xa9\' 作为文字源字符或 UCN 应产生编译器诊断和未定义的行为。

\n

但请注意,这"\\\\u00e9"不是前面有反斜杠的 UCN,并且不可能在反斜杠后面有 UCN 的字符串或字符文字中写入任何基本源字符序列。因此"\\\xc3\xa9"和 的"\\\\u00e9"行为不要求相同: 的行为"\\\\u00e9"可以被完美定义,而 的行为"\\\xc3\xa9"则未定义。

\n

如果我们要假设一些允许反斜杠转义 UCN 的语法,比如说"\\\xc2\xab\\u00e9\xc2\xbb",那么就会有未定义的行为,例如"\\\xc3\xa9"

\n
\n
\n
    \n
  • 在第一阶段,printf("\\\xc3\xa9\\n");映射到printf("\\\\u00e9\\n");
  • \n
\n
\n

到 UCN的第一阶段转换\xc3\xa9无法创建非 UCN,例如"\\\\u00e9".

\n
\n

编译器是正确的,但没有用完美的诊断消息专门处理这种情况。理想情况下你会得到的是:

\n
$ clang++ -std=c++11  -Wall -Wextra ucn.cpp -o ucn\nucn.cpp:4:10: warning: unknown escape sequence \'\\\xc3\xa9\' [-Wunknown-escape-sequence]\n        printf("\\\xc3\xa9\\n");\n                ^\n1 warnings generated.\n$ ./ucn\n\xc3\xa9\n\\u00e9\n
Run Code Online (Sandbox Code Playgroud)\n

两个编译器都指定它们在存在未知转义序列时的行为是用转义的字符替换转义序列,因此"\\\xc3\xa9"将被视为"\xc3\xa9"并且整个程序应解释为:

\n
#include <stdio.h>\n\nint main(void){\n        printf("\xc3\xa9\\n");\n        printf("\\\\u00e9\\n");\n        return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

两个编译器确实碰巧出现了这种行为,部分是偶然的,但也部分是因为以它们的方式处理无法识别的转义序列的策略是一个明智的选择:即使它们只将无法识别的转义序列视为反斜杠后跟字节 0xC3 ,他们删除反斜杠并将 0xC3 保留在原处,这意味着 UTF-8 序列保持不变以供以后处理。

\n