【问题标题】:Unicode Identifiers and Source Code in C++11?C ++ 11中的Unicode标识符和源代码?
【发布时间】:2011-04-15 12:49:16
【问题描述】:

我在新的 C++ 标准中找到

2.11 Identifiers                  [lex.name]
identifier:
    identifier-nondigit
    identifier identifier-nondigit
    identifier digit
identifier-nondigit:
    nondigit
    universal-character-name
    other implementation-defined character

附加文字

标识符是任意长的字母和数字序列。每个universal-character-name in an identifier应指定一个字符,其在ISO 10646中的编码属于指定范围之一 在 E.1 中。 [...]

我不能完全理解这意味着什么。例如,从旧的标准中,我习惯于写一个“通用字符名称”\u89ab。但是在标识符中使用那些......?真的吗?

新标准是否对 Unicode 更开放?我并没有提到新的 Literal Types "uHello \u89ab thing"u32,我想我理解那些。但是:

  • (便携式)源代码是否可以采用任何 unicode 编码,例如 UTF-8、UTF-16 或任何(如何定义)代码页?
  • 我可以写一个带有\u1234 的标识符吗myfu\u1234ntion(无论出于何种目的)
  • 或者我可以使用 unicode 在 ICU 中定义的“字符名称”,即

    const auto x = "German Braunb\U{LOWERCASE LETTER A WITH DIARESIS}r."u32;
    

    甚至在源本身的标识符中?那将是一种享受... 咳嗽...

我认为所有这些问题的答案都是,但我无法将其可靠地映射到标准中的措辞... :-)

编辑:我发现“2.2 翻译阶段 [lex.phases]”,第 1 阶段:

如有必要,物理源文件字符以实现定义的方式映射到基本源字符集 [...]。接受的物理源文件字符集是实现定义的。 [...]任何不在基本文件中的源文件字符 源字符集 (2.3) 被指定该字符的通用字符名替换。 (实现可以使用任何内部编码,只要在源文件中遇到的实际扩展字符,以及在源文件中表示为通用字符名称的相同扩展字符(即,使用 \uXXXX 表示法)是等效处理,除非此替换在原始字符串文字中恢复。)

通过阅读本文,我现在认为,编译器可以选择接受 UTF-8、UTF-16 或它希望的任何代码页(通过元信息或用户配置)。在第 1 阶段,它将其转换为 ASCII 形式(“基本源字符集”),然后将 Unicode 字符替换为其 \uNNNN 表示法(或者编译器可以选择继续以它的 Unicode 表示形式工作,但是而不是必须确保它以相同的方式处理其他\uNNNN

你怎么看?

【问题讨论】:

标签: unicode syntax c++11


【解决方案1】:

新标准是否对 Unicode 更加开放?

关于允许在标识符中使用通用字符名称,答案是否定的;早在 C99 和 C++98 中就允许在标识符中使用 UCN。然而,编译器直到最近才实现该特定要求。我认为 Clang 3.3 引入了对此的支持,并且 GCC 已经为此提供了一个实验性功能已有一段时间了。 Herb Sutter 在他的 Build 2013 演讲“C++ 的未来”中也提到,这个特性也将在某个时候出现在 VC++ 中。 (尽管 IIRC Herb 将其称为 C++11 功能;它实际上是 C++98 功能。)

预计不会使用 UCN 编写标识符。相反,预期的行为是使用源编码写入所需的字符。例如,源代码如下所示:

long pörk;

不是:

long p\u00F6rk;

但是,UCN 也可用于其他目的;并非所有编译器都必须接受相同的源编码,但现代编译器都支持某些编码方案,其中至少基本源字符具有相同的编码(也就是说,现代编译器都支持某些 ASCII 兼容编码)。

UCN 允许您仅使用基本字符编写源代码,但仍可命名扩展字符。这在例如在源代码中编写字符串文字“°”时很有用,它将被编译为 CP1252 和 UTF-8:

char const *degree_sign = "\u00b0";

这个字符串文字在多个编译器上被编码成适当的执行编码,即使源编码不同,只要编译器至少对基本字符共享相同的编码。

(便携式)源代码可以采用任何 unicode 编码,例如 UTF-8、UTF-16 或任何(如何定义)代码页?

标准没有要求,但大多数编译器都会接受 UTF-8 源代码。 Clang 支持only UTF-8 源(尽管它对字符和字符串文字中的非 UTF-8 数据有一些兼容性),gcc 允许指定源编码并包括对 UTF-8 的支持,并且VC++会猜测编码,并且可以猜测UTF-8。

(更新:VS2015 现在提供了option 来强制源和执行字符集为 UTF-8。)

我可以在其中写一个带有 \u1234 的标识符吗 myfu\u1234ntion(无论出于何种目的)

是的,规范规定了这一点,尽管正如我所说,并非所有编译器都实现了这一要求。

或者我可以使用 unicode 在 ICU 中定义的“字符名称”,即

const auto x = "German Braunb\U{LOWERCASE LETTER A WITH DIARESIS}r."u32;

不,您不能使用 Unicode 长名称。

甚至在源本身的标识符中?那将是一种享受......咳嗽......

如果编译器支持包含您想要的扩展字符的源代码编码,那么在源代码中按字面书写的字符必须与等效的 UCN 完全相同对待。所以是的,如果您使用支持 C++ 规范这一要求的编译器,那么您可以直接在源代码中编写其源字符集中的任何字符,而无需编写 UCN。

【讨论】:

  • 我希望您能为此获得“接受迟到的答复”徽章。 “如果编译器支持源代码编码” 似乎是棘手的部分。据我了解,您的编译器不需要 支持任何这些可爱的编码,只要它们理解 UCN 即可——目前并非所有编译器都这样做(因此在这方面不符合标准)。
  • 这意味着对于可移植代码,假设所有涉及的编译器都是兼容的(并且在理论上),我必须使用 UCN 为 unicode 字符编写我的 ASCII 源代码,对吗?任何依赖源代码编码都没有“可移植性”。
  • @towi 规范甚至不需要 ASCII;兼容的编译器只能支持 EBCDIC,因此除非我们计算手动编码转换,否则没有源代码是真正可移植的。也不是所有的 ASCII 字符都在基本的源字符集中;您必须避免使用字符“$”、“`”和“@”(当然,UCN 除外)。
  • @bames53 Yes, the specification mandates this, although as I said not all compilers implement this requirement yet. 具体在哪里(在最近的草案中)要求这样做?
  • @Belloc 查看 [lex.name],第 5.10 节 n4835 中的标识符。语法指定标识符以包含 UCN,表 2 指定允许使用的字符,包括高代码点,例如将写为 \uXXXX 和实际上 \u1234 专门允许在标识符中。这是一个埃塞俄比亚字符,ሴ,显然。
【解决方案2】:

我认为其意图是允许标识符中包含Unicode字符,例如:

long pöjk;
ostream* å;

【讨论】:

  • 想评论否决票?请告诉我的答案是否不正确。
  • 我没有投反对票,但我认为您的回答不太正确。到目前为止,我发现“2.2.(1) 翻译阶段”: 物理源文件字符[例如 Unicode] 以实现定义的方式映射到基本源字符集。 [...] 接受的物理源文件字符集是实现定义的。 [...] 任何不在基本源字符集 (2.3) 中的源文件字符都将替换为指定该字符的通用字符名称。 [...] 因此,我现在相信,\u1234 在阶段 1 之后 ASCII 形式的标识符中的预期符号。
  • @towi 指定这种方式的原因是因为通用字符名称,即\uXXXX\UXXXXXXXX 是语法引用基本字符集之外的字符的唯一方式。 'as-if' 规则允许编译器避免实际将扩展字符转换为 UCN,并且规范实际上明确说明了这一点:“实现可以使用任何内部编码,只要在源文件中遇到实际扩展字符,并且在源文件中表示为通用字符名 [...] 的相同扩展字符被等效处理 [...]"。
【解决方案3】:

我建议使用clang++ 而不是g++。 Clang 旨在与 GCC (wikipedia-source) 高度兼容,因此您很可能只需替换该命令即可。

我想在我的源代码中使用希腊符号。 如果代码可读性是目标,那么使用(例如)α 而不是alpha 似乎是合理的。尤其是在较大的数学公式中使用时,它们可以在源代码中更容易阅读。

为了实现这一点,这是一个最小的工作示例:

> cat /tmp/test.cpp
#include <iostream>

int main()
{
    int α = 10;
    std::cout << "α = " << α << std::endl;
    return 0;
}
> clang++ /tmp/test.cpp -o /tmp/test
> /tmp/test 
α = 10

【讨论】:

    【解决方案4】:

    这篇文章https://www.securecoding.cert.org/confluence/display/seccode/PRE30-C.+Do+not+create+a+universal+character+name+through+concatenation 认为int \u0401; 是兼容代码,尽管它基于C99,而不是C++0x。

    【讨论】:

    • 非常好的观点。也有一个 C++ 规则。 securecoding.cert.org/confluence/display/cplusplus/…。我同意我可以使用\u...-notation 编写标识符,是的。但是文件本身是 ASCII,并且是通过早期的和实现定义的步骤从编码文件到这个 ASCII 表示生成的。你同意吗?
    • 我认为文件通常是 ASCII。 “物理源文件字符 (ASCII) 以实现定义的方式映射到基本源字符集(Unicode 的某些变体)......”
    【解决方案5】:

    目前的 gcc 版本(目前为止是 5.2 版)仅支持 ASCII 并且在某些情况下支持 EBCDIC 输入文件。因此,标识符中的 unicode 字符必须使用 ASCII 编码文件中的 \uXXXX 和 \UXXXXXXXX 转义序列来表示。虽然在 EBCDIC 编码的输入文件中可以将 unicode 字符表示为 ??/uXXXX 和 ??/UXXXXXXX,但我尚未对此进行测试。无论如何,只要安装了最新版本的 iconv,一个简单的 cpp 单行补丁就可以直接读取 UTF-8 输入。详情在

    https://www.raspberrypi.org/forums/viewtopic.php?p=802657

    并且可以通过补丁进行总结

    diff -cNr gcc-5.2.0/libcpp/charset.c gcc-5.2.0-ejo/libcpp/charset.c
    *** gcc-5.2.0/libcpp/charset.c  Mon Jan  5 04:33:28 2015
    --- gcc-5.2.0-ejo/libcpp/charset.c  Wed Aug 12 14:34:23 2015
    ***************
    *** 1711,1717 ****
        struct _cpp_strbuf to;
        unsigned char *buffer;
    
    !   input_cset = init_iconv_desc (pfile, SOURCE_CHARSET, input_charset);
        if (input_cset.func == convert_no_conversion)
          {
            to.text = input;
    --- 1711,1717 ----
        struct _cpp_strbuf to;
        unsigned char *buffer;
    
    !   input_cset = init_iconv_desc (pfile, "C99", input_charset);
        if (input_cset.func == convert_no_conversion)
          {
            to.text = input;
    

    【讨论】:

    • EBCDIC 中的 UCS 字符似乎很有趣。感谢您的参考。
    猜你喜欢
    • 1970-01-01
    • 2013-08-31
    • 2013-06-24
    • 2010-09-24
    • 1970-01-01
    • 2015-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多