为什么要在 UTF-8 中标记连续字节?
Why is it necessary to mark continuation bytes in UTF-8?
我最近一直在阅读 UTF-8 可变宽度编码,我发现 UTF-8 将每个连续字节的前两位指定为 10 很奇怪。
Range | Encoding
-----------------+-----------------
0 - 7f | 0xxxxxx
80 - 7ff | 110xxxx 10xxxxxx
800 - ffff | 1110xxx 10xxxxxx 10xxxxxx
10000 - 10ffff | 11110xx 10xxxxxx 10xxxxxx 10xxxxxx
我正在尝试其他可能的可变宽度编码,发现通过使用以下方案,最多需要 3 个字节来存储所有 Unicode。如果第一位是 1,则该字符至少再编码一个字节(读取直到第一位为 0)。
Range | Encoding
-----------------+-----------------
0 - 7f | 0xxxxxx
80 - 407f | 1xxxxxx 0xxxxxxx
4080 - 20407f | 1xxxxxx 1xxxxxxx 0xxxxxxx
UTF-8 中的连续位真的那么重要吗?第二种编码似乎更有效率。
UTF-8 是自验证的,前进速度快,后退更容易。
自验证: 由于序列中的第一个字节指定了长度,接下来的 X 个字节必须适合 10xxxxxx
,否则您的序列无效。单独看到一个 10xxxxxx
字节会立即被识别为无效。
您建议的编码没有内置验证。
快速前进:如果您必须跳过字符,您可以立即跳过第一个字节确定的 X 个字节,而无需检查每个中间字节。
更容易倒退:如果你必须倒退读取字节,你可以通过10xxxxxx
立即识别一个连续字符。然后,您将能够向后扫描 10xxxxxx
字节以获得 11xxxxxx
前导字节,而不必扫描前导字节。
使用您提出的方案,如果您查看编码为 0xxxxxxx 的字节,则无法判断它是单字节单元 0x00..0x7F 还是多字节单元的最后一个字节。您必须向后扫描并查看前面的字节才能知道(并且您必须向后检查两个单位以查看它是否是 2 或 3 字节代码点的最后一个字节)。如果你有一个 1xxxxxxx 字节,你无法分辨它是多字节单元的第一个字节还是中间字节。同样,您必须向后扫描。
相比之下,UTF-8 方案允许您为任何非连续字节判断有多少后续字节是代码点的一部分。对于连续字节,您只需向后扫描到起始字节。您还会得到错误检查; UTF-8 中有许多无效序列,这实际上是一个好处。 (字节 0xC0、0xC1、0xF5..0xFF 不能出现在有效的 UTF-8 中。)
除了已经提到的易于迭代之外:UTF-8 旨在让基于 ASCII(和其他 UTF-8 不支持)的工具安全地处理搜索、连接、替换和转义等常见操作.
ASCII 兼容性在互操作性和安全性方面的优势超过了为字符 U+0800 到 U+407F 使用额外字节的成本。
80 - 407f | 1xxxxxx 0xxxxxxx
所以有一些东亚多字节编码就是这样做的,但有一些 UTF-8 专门试图避免的不幸结果。
在这个提议的方案中,连续字节现在与 ASCII 重叠,许多 ASCII 字符对不同的语言和工具有特殊的含义。因此,如果你想说 ¢
那是 0x80,0x27 并且它的第二个字节看起来像 "
任何操作字节字符串的工具,而不支持和知道该数据使用的建议编码.
提示将用户输入合并到控制流中的所有内容中的安全漏洞。 SQL 查询注入,HTML 网页注入,shell 脚本命令注入等等。
(东亚多字节编码并没有这里的这种编码那么糟糕,因为它们没有将 ASCII 控制代码重新用作连续字节。按照建议,使用这种编码的文本不能存储在例如,C 空字符终止字符串。不过,Shift-JIS 和它的朋友造成了一大堆安全漏洞,我们都很高兴摆脱它们。)
我最近一直在阅读 UTF-8 可变宽度编码,我发现 UTF-8 将每个连续字节的前两位指定为 10 很奇怪。
Range | Encoding
-----------------+-----------------
0 - 7f | 0xxxxxx
80 - 7ff | 110xxxx 10xxxxxx
800 - ffff | 1110xxx 10xxxxxx 10xxxxxx
10000 - 10ffff | 11110xx 10xxxxxx 10xxxxxx 10xxxxxx
我正在尝试其他可能的可变宽度编码,发现通过使用以下方案,最多需要 3 个字节来存储所有 Unicode。如果第一位是 1,则该字符至少再编码一个字节(读取直到第一位为 0)。
Range | Encoding
-----------------+-----------------
0 - 7f | 0xxxxxx
80 - 407f | 1xxxxxx 0xxxxxxx
4080 - 20407f | 1xxxxxx 1xxxxxxx 0xxxxxxx
UTF-8 中的连续位真的那么重要吗?第二种编码似乎更有效率。
UTF-8 是自验证的,前进速度快,后退更容易。
自验证: 由于序列中的第一个字节指定了长度,接下来的 X 个字节必须适合 10xxxxxx
,否则您的序列无效。单独看到一个 10xxxxxx
字节会立即被识别为无效。
您建议的编码没有内置验证。
快速前进:如果您必须跳过字符,您可以立即跳过第一个字节确定的 X 个字节,而无需检查每个中间字节。
更容易倒退:如果你必须倒退读取字节,你可以通过10xxxxxx
立即识别一个连续字符。然后,您将能够向后扫描 10xxxxxx
字节以获得 11xxxxxx
前导字节,而不必扫描前导字节。
使用您提出的方案,如果您查看编码为 0xxxxxxx 的字节,则无法判断它是单字节单元 0x00..0x7F 还是多字节单元的最后一个字节。您必须向后扫描并查看前面的字节才能知道(并且您必须向后检查两个单位以查看它是否是 2 或 3 字节代码点的最后一个字节)。如果你有一个 1xxxxxxx 字节,你无法分辨它是多字节单元的第一个字节还是中间字节。同样,您必须向后扫描。
相比之下,UTF-8 方案允许您为任何非连续字节判断有多少后续字节是代码点的一部分。对于连续字节,您只需向后扫描到起始字节。您还会得到错误检查; UTF-8 中有许多无效序列,这实际上是一个好处。 (字节 0xC0、0xC1、0xF5..0xFF 不能出现在有效的 UTF-8 中。)
除了已经提到的易于迭代之外:UTF-8 旨在让基于 ASCII(和其他 UTF-8 不支持)的工具安全地处理搜索、连接、替换和转义等常见操作.
ASCII 兼容性在互操作性和安全性方面的优势超过了为字符 U+0800 到 U+407F 使用额外字节的成本。
80 - 407f | 1xxxxxx 0xxxxxxx
所以有一些东亚多字节编码就是这样做的,但有一些 UTF-8 专门试图避免的不幸结果。
在这个提议的方案中,连续字节现在与 ASCII 重叠,许多 ASCII 字符对不同的语言和工具有特殊的含义。因此,如果你想说 ¢
那是 0x80,0x27 并且它的第二个字节看起来像 "
任何操作字节字符串的工具,而不支持和知道该数据使用的建议编码.
提示将用户输入合并到控制流中的所有内容中的安全漏洞。 SQL 查询注入,HTML 网页注入,shell 脚本命令注入等等。
(东亚多字节编码并没有这里的这种编码那么糟糕,因为它们没有将 ASCII 控制代码重新用作连续字节。按照建议,使用这种编码的文本不能存储在例如,C 空字符终止字符串。不过,Shift-JIS 和它的朋友造成了一大堆安全漏洞,我们都很高兴摆脱它们。)