【问题标题】:Is this UTF-8 implementation implementation-defined or well-defined?这个 UTF-8 实现是实现定义的还是定义明确的?
【发布时间】:2016-04-06 06:27:14
【问题描述】:

我只是四处寻找一些 UTF-8 代码点的实现(不,不是抄袭),偶然发现了this

typedef unsigned char char8_t;
typedef std::basic_string<unsigned char> u8string;

这段代码是否忽略了CHAR_BIT 只需要至少为8,但可能更大的事实?或者在这种情况下这无关紧要并且代码很好?如果是这样,那这是为什么呢?

另外,有人(大概是 SO 成员@NicolBolas?)写道:

const char *str = u8"This is a UTF-8 string.";

这几乎就是 UTF-8 在 C++ 中用于字符串文字的方式。

我认为 UTF-8 中的代码单元总是正好是八位!
来自 Unicode 标准 8.0.0,第 2.5 章:

在Unicode字符编码模型中,精确定义的编码 表单指定 Unicode 字符的每个整数(代码点)如何 表示为一个或多个代码单元的序列。统一码 标准为 Unicode 提供了三种不同的编码形式 字符,使用 8 位、16 位和 32 位单位。这些是 分别命名为 UTF-8、UTF-16 和 UTF-32。

(删除了换行符,删除了换行符上的连字符,添加了强调。)

那么他为什么声称使用const char* 而不是const uint8_t*(或建议的、假设的const char8_t*)?

【问题讨论】:

  • @skypjack 我回滚到我的版本。第一个代码是一个引用,即使没有真正的文本。
  • 一个很好的问题,但引号中几乎没有视觉缺陷,所以我删除了它们,仅此而已。我错了吗,是我的移动应用程序没有正确格式化问题?
  • @skypjack 嗯...看起来并不好,让我们用外交的方式来表达。 :-)
  • @skypjack:您的编辑 (a) 删除了第一个代码块的引用标记,(b) 删除了第二个引用的代码标记,并且 (c) 丢失了已添加的新文本在之前的修订中。在那次编辑中没有任何有用的东西,无论是移动还是其他!
  • 该死的,我很抱歉。我刚刚发现:(a)移动应用程序在引用中显示代码时一团糟(我以前从未注意到过)和(b)如果我提交的编辑在与新版本的问题/答案冲突。好吧,让我写下不再编辑问题/答案,除非我是从网络上进行的。对于给您带来的不便,我深表歉意,您回滚它是对的,绝对!!

标签: c++ string unicode utf-8


【解决方案1】:

uint8_t 仅存在于具有恰好 8 位可访问内存的系统上。 UTF-8 没有任何这样的要求。它使用适合 8 位的值,但对这些值的实际存储方式没有任何要求。每个 8 位值可以存储为 16 位或 32 位或任何对它运行的系统有意义的值;唯一的要求是值必须正确。

【讨论】:

  • 我无法理解这一点。让我们使用一个 UTF-8 字符串。每个代码单元都是八位。现在将其存储在 RAM 中,每个字节为 9 位。这些不同的字节单元不会以某种方式重叠并导致巨大的灾难吗?
  • @cad:当然不是,如果你把这个字符串存储在 9 位字节内存中,每个字符仍然存储在一个单独的字节中。
  • @cad - 不,这不是问题。如果内存被组织为 9 位块,则编译器将例如 3 个 8 位值存储在 3 个 9 位块中。这 3 个值不受它们存储方式的影响。
  • @cad - 必须存储的 范围从 0 到 255;这就是“8 位值”的含义。只要保留这些值,它们的存储方式就无关紧要。在具有可寻址 8 位存储的系统上,将这些值存储在该 8 位存储中显然是有意义的。在具有 9 位存储的系统上,您可以毫无问题地将值 0-255 存储在 9 位中,并且您的代码不会知道其中的区别。
  • @cad - 忘记八位字节。重要的是价值观 1 可以存储为 64 位 int 并且仍然是 1。没有移位,没有魔法。它只是工作。
【解决方案2】:

那么他为什么声称使用const char* 而不是const uint8_t*(或建议的、假设的const char8_t*)?

因为这就是标准所说的。 u8 文字字符串将解析为 const char[N] 类型的数组。这就是 C++ 中定义 UTF-8 文字的方式。

如果系统上的char 有超过 8 位...就这样吧。字符串中的每个char 仍将包含一个介于 0 和 255 之间的值,这是有效 UTF-8 代码单元的范围。即使char 可以在这样的系统上保存较大的值。

如果char 不能容纳 8 位...那么实现无效。根据该标准的最新措辞,char 需要保存足够的位来存储每个有效的 UTF-8 代码单元。从技术上讲,255 不是有效的 UTF-8 代码单元。

事实是这样的:已经有大量的代码通过char* 接受 UTF-8。他们不会重写 POSIX、文件系统 API 和其他任何东西来采用不同的类型。

话虽如此,通过const char* 操作一系列 UTF-8 代码单元是……可疑的。这是因为他们可以签名。但是,最近的标准措辞要求unsigned charchar 之间的转换在有效的UTF-8 代码单元范围内工作。也就是说,您可以将const char* 转换为const unsigned char*,对其进行位操作,然后再将其转换回去,保证您可以正常工作。

那个超级复杂的“标准的最新措辞”有什么意义?

这样做的目的是让 UTF-8 字符串真正工作。因为标准委员会以他们的“无限智慧”决定不包含特殊的char8_t UTF-8 代码单元类型,所以他们必须添加措辞以使char 担任该角色。这要求与unsigned charchar 之间的转换不能破坏UTF-8 代码单元。

甚至还有一个discussion topic on the C++ standard discussion forums,其中wording was discussed (search for 1759)。 C++14 的措辞说:

对于在 0 到 255 范围内的 unsigned char 类型的每个值 i,存在一个 char 类型的值 j,使得从 i 到的整数转换 (4.7) 的结果charj,从junsigned char 的积分转换结果是i

这尤其意味着char 只有在签名表示满足上述条件时才能默认签名。一个有符号的补码 char 是不够的,因为负零有一个特殊的表示 (0x80),当转换为无符号时,它会变成常规的 0。

他们是否应该刚刚定义了一个特定的char8_t,该char8_t 必须是无符号的并且至少有8 位?大概。但它已经完成并且没有改变。

【讨论】:

  • "从技术上讲,255 不是有效的 UTF-8 代码单元。"什么。 255 适合 8 位,那么为什么不适合 UTF-8 代码单元呢?那个超级复杂的“标准的最新措辞”有什么意义?位操作是什么意思?我认为 const char* 在取消引用时不允许修改。
  • 为什么是char 而不是unsigned char?如果char 签名了怎么办?那不是实现定义的行为吗?
  • 255 fits into 8 bits, so why not into a UTF-8 code unit? 如果您查看 UTF-8 编码的详细信息,可以成为有效 UTF-8 序列一部分的每个八位字节都至少有一个 0 位。我认为最大可能的八位字节是11110111b == 247
  • @cad:“位操作是什么意思?” char 最终只是一个数字,一个整数。您可以对整数进行位操作。 UTF-8 处理需要这样做。
  • @NicolBolas 没有说“255 不适合 [whatever]”——你说了(现在正在反对你自己创造的稻草人)。他或她说,我引用,“255 不是有效的 UTF-8 代码单元”——我认为这意味着“值为 255 的八位字节不能出现在有效的 UTF-8 序列中”,这是真的声明。
【解决方案3】:

[lex.string]/8 普通字符串文字和 UTF-8 字符串文字也称为窄字符串文字。窄字符串文字的类型为“n const char 数组”,其中n 是字符串的大小,定义如下,并且具有静态存储持续时间 (3.7)。

因此,无论其他情况如何,UTF-8 字符串文字都是chars 的序列。

至于uint8_t

7.20.1.1

2 typedef 名称uintN_t 指定宽度为N 且无填充位的无符号整数类型。因此,uint24_t 表示这样一种无符号整数类型,其宽度正好为 24 位。

3 这些类型是可选的。但是,如果实现提供了宽度为 8、16、32 或 64 位的整数类型,没有填充位,并且(对于有符号类型)具有二进制补码表示,则它应定义相应的 typedef 名称。

char 大于 8 位的假设系统上,uint8_t 不会被定义。

【讨论】:

  • 好吧,这是标准的版本,但这是为什么呢?为什么可以合法地将 UTF-8 字符串称为const char*
  • 我认为没有更好的选择。您更愿意如何定义它?
  • "在一个 char 大于 8 位的假设系统上,uint8_t 不会被定义。"我问这个事实在聊天(Lounge)中是否正确,他们告诉我这是错误的,因为某些实现定义可以用于实现uint8_t
  • char 表示给定架构上的最小可寻址单元。如果可以定义uint8_t,那么也可以谨慎地将char 定义为8 位。 "[basic.types]/2 对于任何可简单复制的类型 T 的对象 ... 构成该对象的底层字节 (1.7) 可以复制到 @987654337 的数组中@ 或 unsigned char。”
  • "[intro.memory]/1 C++内存模型中的基本存储单元是byte。一个byte至少足够大包含...... Unicode UTF-8 编码形式的八位代码单元,由连续的位序列组成,其数量由实现定义。”
【解决方案4】:

UTF-8 中的代码单元总是正好是八位unsigned char 被指定为至少 8 位,因此 UTF-8 中的所有代码单元都适合 unsigned char 类型。

u8"This is a UTF-8 encoded string constant" 的基本原理不是它以 8 位字节存储,而是它被编码为 UTF-8,而源文件可能具有不同的编码。 u8string typedef 与此一致,但如果字节超过 8 位,则会有点混乱。

使用unsigned char 是消除char 类型签名不确定性的好方法。

【讨论】:

    【解决方案5】:

    char8_t 在圣地亚哥会议期间被投票支持 C++20,因此此代码无法编译。

    但是,您将能够使用std::u8string,但请记住,它仅适用于代码单元,而不适用于代码点或字素簇,因此安全的方法是将其视为不透明的 blob 并使用 3rd 方库对其进行变异。至少现在是这样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-14
      • 2019-10-29
      • 1970-01-01
      • 1970-01-01
      • 2013-09-02
      • 1970-01-01
      相关资源
      最近更新 更多