【发布时间】:2021-03-19 21:07:27
【问题描述】:
假设您有一个 UTF-8 编码的字符串 s。您提取似乎是 UTF-8 编码代码点的第一个字节,并将它们放入一个 32 位整数 c。
例如:
- 如果您有
s="AB"(即{0x41,0x42,0x00}),则c将是0x41; - 如果您有
s="èB"(即{0xC3,0xA8,0x42,0x00})c将是0xC3A8;
问题是检查c 是否为有效编码。
我写的函数是下面的函数,但我不确定这是否正确(我可能会错过一些边缘情况)。
我知道我可以按照标准指定的 FSM 逐字节进行,但我需要检查这是否是正确的方法。
int chr_isvalid(uint32_t c)
{
if (c <= 0x7F) return 1;
if (0xC080 == c) return 1; // Accept 0xC080 as representation for '\0'
if (0xC280 <= c && c <= 0xDFBF) return ((c & 0xE0C0) == 0xC080);
if (0xEDA080 <= c && c <= 0xEDBFBF) return 0; // Reject UTF-16 surrogates
if (0xE0A080 <= c && c <= 0xEFBFBF) return ((c & 0xF0C0C0) == 0xE08080);
if (0xF0908080 <= c && c <= 0xF48FBFBF) return ((c & 0xF8C0C0C0) == 0xF0808080);
return 0;
}
澄清:
- 请看我的自我回复,看看为什么我认为这段代码是正确的。
- 我不想猜测这是 UTF-8 还是任何其他编码。假设字符串是 UTF-8 编码的。
- 仅查看字节中的起始 1 即可提取候选代码点,但这无关紧要,因为该函数必须适用于
c的任何值。 - 任何有效代码点 (U+000000 - U+10FFFF) 的编码都适合 32 位整数。
- 我需要以编码形式提取的
c用于其他目的 - 感谢 Jonathan Leffler 下面对 UTF-16 代理 U+D800 - U+DFFF 的评论,我现在拒绝它们。
- 感谢 Jonathan Leffler 下面关于超长编码的评论,我修复了它,现在应该是正确的。任何 2 字节超长编码必须小于 0xC280,任何 3 字节超长编码必须小于 0xE0A080,任何 4 字节超长编码必须小于 0xF0908080。我过滤掉了。
为了更好地查明我的错误,请提供此代码拒绝的有效编码代码点或此代码接受为有效的无效编码的示例。
【问题讨论】:
-
是否有理由需要将字节加载到整数中?这似乎使问题更难编写和理解。为什么不把字节当作字节来处理呢?
-
@JoelFan 说了什么。此外,您提出的代码没有意义。看起来它希望 caller 已经砍掉了正确的字节数,这只有在调用者已经完成工作的情况下才有可能。但也许这只是给你的奇怪的(非常人为的,最有可能的)问题场景......?
-
@JoelFan:所有 UTF-8 字符都是 1-4 个字节,因此 4 个字节足以确定下一个字节是否构成字符。
-
您的测试将接受非最小 UTF-8 代码作为有效代码,而实际上它们不是。您的测试将接受 UTF-16 代理代码点为有效且它们也无效。不,您的测试不会确认这些字节不是来自不同的编码,例如 ISO 8859-x 或 UTF-16。
-
你有
if (0xC080 == c) return 1; // Accept 0xC080 as representation for '\0'— 这是'\0'的无效 UTF-8 编码,在应该严格检查有效性的函数中没有位置。