jli*_*ing 3 string ascii utf-8 character-encoding
最高位编码方式
如果一个字节以 0 开头,则它是最终字节,如果一个字节以 1 开头,则它不是最终字节
请注意,这也是 ASCII 兼容的,事实上,这就是 LLVM 位码的作用。
UTF-8 具有 ULEB128 所缺乏的关键属性,即 UTF-8 字符编码不是任何其他 UTF-8 字符的子字符串。为什么这很重要?它允许 UTF-8 满足绝对重要的设计标准:您可以将适用于单字节编码(如 ASCII 和 Latin-1)的 C 字符串函数应用到 UTF-8,并且它们将正常工作。正如我们将看到的,ULEB128 的情况并非如此。这反过来意味着,大多数为使用 ASCII 或 Latin-1 编写的程序在传递 UTF-8 数据时也会“正常工作”,这对于在旧系统上采用 UTF-8 来说是一个巨大的好处。
\n首先,这里简单介绍一下 ULEB128 编码的工作原理:每个代码点都被编码为以没有设置高位的字节结尾的字节序列\xe2\x80\x94,我们将其称为“低字节”(值 0- 127);每个前导字节都有高位组\xe2\x80\x94,我们将其称为“高字节”(值128-255)。由于 ASCII 字符为 0-127,因此它们都是低字节,因此在 ULEB128(以及 Latin-1 和 UTF-8)中的编码方式与 ASCII 中的编码方式相同:即任何 ASCII 字符串也是有效的 ULEB128 字符串(以及 Latin- 1 和 UTF-8),编码相同的代码点序列。
\n请注意子字符属性:任何 ULEB128 字符编码都可以使用任何 127 个高字节值作为前缀,以编码更长、更高的代码点。UTF-8 不会出现这种情况,因为前导字节对后面的字节数进行编码,因此任何其他字节中都不能包含 UTF-8 字符。
\n为什么这个属性很重要?一种特别糟糕的情况是,任何代码点可被 128 整除的字符的 ULEB128 编码都将包含尾随 NUL 字节 ( 0x00)。例如,字符\xc4\x80(大写“A”,上面带有横线)的代码点 U+0100 将在 ULEB128 中编码为字节0x82和0x00。由于许多 C 字符串函数将 NUL 字节解释为字符串终止符,因此它们会错误地将第二个字节解释为字符串的结尾,并将0x82字节解释为无效的悬空高字节。另一方面,在 UTF-8 中,NUL 字节仅出现在 NUL 字节代码点 U+00 的编码中,因此不会出现此问题。
同样,如果您在strchr(str, c)ULEB128 编码的字符串中搜索 ASCII 字符,则可能会出现误报。这是一个例子。假设您有以下非常 Unicode 文件路径:/\xe2\x98\xaf. 这可能是您的卷饼食谱收藏中的一个文件,用于您称为“阴阳”的卷饼。该路径名具有以下代码点序列:
\xe2\x80\x94 统一码 U+1F32F/\xe2\x80\x94 ASCII/Unicode U+2F\xe2\x98\xaf\xe2\x80\x94 Unicode U+262F在 ULEB128 中,这将使用以下字节序列进行编码:
\n10000111\xe2\x80\x93 (U+1F32F)的前导字节11100110\xe2\x80\x93 (U+1F32F)的前导字节00101111\xe2\x80\x93 (U+1F32F)的最后一个字节00101111\xe2\x80\x93 仅/(U+2F)的字节11001100\xe2\x98\xaf\xe2\x80\x93 (U+262F)的前导字节00101111\xe2\x80\x93 \xe2\x98\xaf(U+262F)的最后一个字节如果您使用strchr(path, '/')搜索斜杠来将此路径拆分为路径组件,您将得到 in 中最后一个字节和 中最后一个字节的虚假匹配\xe2\x98\xaf,因此对于 C 代码来说,该路径看起来像是由\\x87\\xE6后面跟着的无效目录名组成两个斜杠,后跟另一个无效的目录名称\\xCC和最后一个尾部斜杠。将此路径与 UTF-8 编码方式进行比较:
11110000\xe2\x80\x94 (U+1F32F)的前导字节10011111\xe2\x80\x94 (U+1F32F)的连续字节10001100\xe2\x80\x94 (U+1F32F)的连续字节10101111\xe2\x80\x94 (U+1F32F)的连续字节00101111\xe2\x80\x94 仅/(U+2F)的字节11110000\xe2\x98\xaf\xe2\x80\x94 (U+262F)的前导字节10011111\xe2\x80\x94 的连续字节\xe2\x98\xaf\xe2\x80\x94 (U+262F)10010000\xe2\x80\x94 的连续字节\xe2\x98\xaf\xe2\x80\x94 (U+262F)10101111\xe2\x80\x94 的连续字节\xe2\x98\xaf\xe2\x80\x94 (U+262F)的编码/是0x2Fwhich 不会\xe2\x80\x94 并且不能\xe2\x80\x94 出现在字符串中的任何其他位置,因为该字符不会出现在其他位置\xe2\x80\x94 和 的最后一个字节而\xe2\x98\xaf不是。所以用来搜索0xAF0x2Fstrchr/UTF-8编码的路径
这种情况与使用strstr(haystack, needle)搜索子字符串类似:使用 ULEB128 这将找到 的实际出现needle,但它也会找到needle第一个字符被其编码扩展实际第一个字符的任何字符替换的情况。另一方面,使用 UTF-8 则不会出现误报\xe2\x80\x94 中needle出现编码的每个地方都haystack只能编码needle而不能是其他字符串编码的片段。
UTF-8 还有许多其他令人愉快的属性,作为子字符属性 \xe2\x80\x94 的推论,但我相信这是真正关键的一个。其中一些其他属性:
\n您可以通过查看第一个字节来预测需要读取多少字节才能解码 UTF-8 字符。使用 ULEB128,在读取最后一个字节之前,您不知道已读取完一个字符。
\n如果您在任何数据中嵌入有效的 UTF-8 字符串,无论有效还是无效,都不会影响有效子字符串的解释。这是属性的概括,即没有字符是任何其他字符的子序列。
\n如果您从中间开始读取 UTF-8 流并且无法向后查看,则可以判断您是处于字符的开头还是字符的中间。使用 ULEB128,您可能会将字符的尾部解码为看起来有效但实际上不正确的不同字符,而使用 UTF-8,您知道要向前跳到整个字符的开头。
\n如果您从中间开始读取 UTF-8 流,并且想要向后跳到当前字符的开头,则可以执行此操作,而无需读取超过字符的开头。使用 ULEB128,您可以在字符开始之前读取一个字节(如果可能),以了解字符的开始位置。
\n为了解决前两点,您可以使用 ULEB128 的小尾数变体,其中您将每个代码点的位按从最低到最高有效的七位为一组进行编码。那么每个字符编码的第一个字节是低字节,后面的字节是高字节。然而,这种编码牺牲了 UTF-8 的另一个令人愉快的特性,即如果您按字节按字典顺序对编码字符串进行排序,它们的排序顺序与按字符按字典顺序对它们进行排序的顺序相同。
\n一个有趣的问题是,考虑到这组令人愉快的属性,UTF-8 是否是不可避免的?我怀疑可能会有一些变化,但变化不大。
\n您想要使用一些不能出现在其他地方的标记来锚定字符的开头或结尾。在 UTF-8 中,这是一个具有 0、2、3 或 4 个前导的字节,表示字符的开始。
\n您希望以某种方式对字符的长度进行编码,而不仅仅是一个字符何时完成的指示符,因为这可以防止将一个有效字符扩展到另一个有效字符。在 UTF-8 中,前导字节中的前导位数表示字符中有多少个字节。
\n您需要开始/结束标记和开头的长度编码,以便您在流的中间知道您是否位于字符的开头,以便您知道完成该字符需要多少字节特点。
\n可能有一些变体可以满足所有这些和字典顺序属性,但由于所有这些限制,UTF-8 开始看起来相当不可避免。
\n| 归档时间: |
|
| 查看次数: |
511 次 |
| 最近记录: |