UTF-8 为什么要这么设计

2018-12-16 00:54:03 +08:00
 Chingim

今天想通过 自己设计一个 Unicode 的 编码 来了解字符编码, 结果和真正的 UTF-8 对比, 发现有一些出入, 搜索不到相应的信息, 所以有了这个小小的疑问, 不知道 UTF-8 是一开始就设计成这样还是逐步演变成这样的?

utf-8 是字符集 unicode 的一种不定长的编码格式, 一个 code point 会用 1-4 个字节表示, 具体用多少字节取决于 codepoint 落在哪个区间.

它具体的编码规则是(来源):

以汉字严为例,演示如何实现 UTF-8 编码。 严的 Unicode 是 4E25 ( 100111000100101 ),根据上表,可以发现 4E25 处在第三行的范围内( 0000 0800 - 0000 FFFF ),因此严的 UTF-8 编码需要三个字节,即格式是 1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的 x,多出的位补 0。这样就得到了,严的 UTF-8 编码是 11100100 10111000 10100101,转换成十六进制就是 E4B8A5。

但是为什么 UTF-8 不用完所有的有效 bit 呢?

UTF-8 是为了省空间而设计的, 是要把这些有效位塞满的吧? 两个字节就把所有的值用上:

难道 UTF-8 让这些位置空着, 就为了不用做额外的加减偏移量的操作? 有了解的老哥来解惑一下么? 有来源就最好了

5795 次点击
所在节点    程序员
24 条回复
lance6716
2018-12-16 01:18:32 +08:00
> 就为了不用做额外的加减偏移量的操作

对,消耗这么一点点空间能让实现更优雅,是程序员通常会想的
DGideas
2018-12-16 01:29:38 +08:00
关于“为什么 UTF-8 不用完所有的有效 bit 呢?”这个问题

https://en.wikipedia.org/wiki/UTF-8#History 上的“ FSS-UTF proposal (1992) ”表格展示了你说的“最大压缩程度”的 UTF 的一种可能实现:the additional loss in compactness is relatively insignificant, but readers now have to look out for invalid encodings to avoid reliability and especially security issues。在 StackExchange 上的( https://softwareengineering.stackexchange.com/questions/262227/why-does-utf-8-waste-several-bits-in-its-encoding )这篇提问和回答也说了,用这种编码方式能够避免你在解析一个多字节序列时误认为它表示一个单字节( ASCII )字符,避免攻击者精心构造一个字符串,使得编码正好表达一系列 ASCII 表示的指令。
GeruzoniAnsasu
2018-12-16 01:31:53 +08:00
前段时间也是在 v 站看到人发的

http://utf8everywhere.org/zh-cn

> UTF-8 编码在设计上保证了一个 ASCII 字符或子字符串永远不会匹配到一个多字节编码的字符中间。这在 UTF-16 中也适用。这两个编码中,多字节编码的码位的编码单元会将 MSB 设为 1。

> 此外,你还可以像在简单的字节数组中一样,直接在一个 UTF-8 编码的字符串中搜索 UTF-8 编码的非 ASCII 的子字符串——无需关注码位边界。这要归功于 UTF-8 的另一个设计特点——一个码位编码的起始字节永远不会与其他码位的尾随字节相同。


第一个字节的高位,在 utf8 中可用来判断这个码点编码成的 utf8 字节串有多长,而低位字节为了实现上面说到的设计,区间就不能包括开头字节的值

比如两位,低字节最多到 10111111,如果编码到 11000000,就无法与开头字节区分
raysonx
2018-12-16 01:42:20 +08:00
有一种针对中文 GB 编码的 SQL 注入方法,称为半字符注入或者宽字符注入,UTF-8 的这种设计则会避免这种攻击方式。
keakon
2018-12-16 01:59:17 +08:00
1100000x 不能出现在 UTF-8 的字节中,因为 110 开头表示是双字节字符的第 1 个字节,但第 2 个字节只能是 10 开头,还剩 6 个有效位,于是一共 7 位有效位,只能表示 0 ~ 127,和单字节的重复了。
imn1
2018-12-16 02:07:57 +08:00
实际上你还没理解 UTF-8

试试用你所说的方法,这些字符,U4E,U25,U4E25,U014E25
他们的二进制该如何排列?放在一起如何区分?
Vegetable
2018-12-16 03:27:32 +08:00
半夜看到帖子里大佬们讨论的问题,都不好意思睡觉了,想赶紧起来学习。
msg7086
2018-12-16 05:02:52 +08:00
靠位移就能解码,按照你这种设计的话还要涉及到数学运算。先不说写程序更容易出 Bug。光说 CPU 运算效率,直接位移速度要快很多,甚至还能直接做成 CPU 指令集,或者利用已有的 SIMD 进行快速处理。你可以搜一下 UTF-8 SIMD 看看别人的快速实现。
Chingim
2018-12-16 05:09:06 +08:00
@DGideas
@GeruzoniAnsasu 抱歉说得不清楚, 我 append 了. 我了解中间字节必须是 10xxxxxx 格式的必要性, 所以我指的不是"占用这两个比特"


@keakon 1100000x 为什么不能出现在 UTF-8 的字节中呢? 11000001 10000000 这样的两个字节应该不会引起歧义吧?


@imn1 如果按照 append 里提到的编码方法:
- 0x4E,0x25 落在单字节区间, 一个字节就表示了, 二进制分别是: 01001110 以及 00100101
- 0x4E25 落在了三字节区间, 在三字节序列里的偏移是 0x45A5, 填入三字节的格式, 最终的二进制: 11100100 10010110 10100101
- 0x014E25 落在了四字节区间, 在四字节序列里的偏移也是 0x45A5, 填入四字节的格式中, 得到: 11110100 10010110 10100101
- 解码器读到这些字节, 应该也可以还原回去
RqPS6rhmP3Nyn3Tm
2018-12-16 05:09:44 +08:00
目测会有 shell code 注入攻击
Chingim
2018-12-16 05:18:33 +08:00
@Chingim

0x014E25 少写了一个字节, 应该是 11110000 10000100 10010110 10100101

@BXIA 这么说还是有安全的考虑在里面的...
@msg7086 嗯, 编解码速度可能也是造成它这么设计的原因, 位移确实比加减省事
AlphaTr
2018-12-16 06:07:48 +08:00
应该就是为了减少偏移,提高编解码效率,不知道有没有其他历史因素
guanaco
2018-12-16 06:56:45 +08:00
应该和老的 CPU 字节宽度有关?
wsxyeah
2018-12-16 08:22:39 +08:00
对于 110xxxxx 10xxxxxx:

如果左边全为 0,那它的 Unicode 码点就跟单字节的是重复的了,这部分的数量就是单字节能表示的容量:128 个字符,也就是楼主奇怪的 2048-1920。
yidinghe
2018-12-16 09:25:34 +08:00
简单解释就是:0,110,1110,11110 是前缀标记,看到这个标记就知道一个字符是从这里开始,而且知道接下来有几个字节要读; 10 是中间标记,当你从一个字节流的某个位置开始读取,如果遇到 10,就知道它不是一个字符的开头,应该换一个位置读起了。
imn1
2018-12-16 13:34:15 +08:00
根据 APPEND2
你有没有想过 U+800 和你说的 U+880 的区别呢?
前者二进制只有一个「 1 」,后者有两个
意味着通过位运算判断 800 前后,只需一个判断,880 前后则需要两个判断(注意说的是位运算,不是逻辑大于小于)
同理 UTF-8 几个边界点都是这样,都是二进制仅有一个 1,并且是位于最左边,适合各种位运算

你 APPEND2 所定边界,并没有比 UTF-8 多多少,却不适合高速判断,还不好记
BlackL
2018-12-16 15:20:38 +08:00
我觉得 @wsxyeah 说的比较对,以双字节为例,10000001 10111111 转换成 Unicode 码点就是 U+007F,而 10000000 10000000 转换成 Unicode 码点就是 U+0000,与 UTF-8 单字节表示的范围重复了,而三字节没有使用的那些范围也和双字节的范围重复,所以必须要避免重复。如果重复的话我觉得从 Unicode 转换成 UTF-8 的时候就不知道是使用哪种长度的字节好了
reus
2018-12-16 17:00:00 +08:00
所以 UTF-8 并不完全为了省空间而设计
somebody
2018-12-16 18:02:03 +08:00
如果楼主从一开始就通过 https://en.wikipedia.org/wiki/UTF-8 学习 UTF8,并且不要跳过任何一个字,就不会有这个问题了。不同来源的信息可信度、准确度不同,如果是深入研究细节,最好找可信度高的来源,不要找二手的
Chingim
2018-12-16 22:04:34 +08:00
@wsxyeah
@BlackL 不会重复的, 有一个计算偏移的操作. 在假想的算法里, 10000000 10000000 表示的码点不是 U+0000 而是 U+0080

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/517938

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX