必须直接读字节并按RFC 3629逐段验证UTF-8合法性,不可依赖BOM或弃用库函数;需以二进制模式读取、显式转unsigned char、用状态机校验各编码长度及非法序列。

直接读字节、按RFC 3629规则逐段验证,别依赖BOM或库函数——BOM可有可无,std::codecvt_utf8已弃用且行为未定义,iconv只转码不校验。
先读前4字节检查BOM,但不能靠它下结论
BOM只是线索,不是判决依据。UTF-8文件可能带\xEF\xBB\xBF,也可能完全没它;而带BOM的文件一定得跳过这3字节再验证正文,否则后续字节解析全错。
- 用
std::ifstream以std::ios::binary模式打开,read(buf, 4)读前4字节到char buf[4] - 严格比对:
buf[0]==0xEF && buf[1]==0xBB && buf[2]==0xBF→ 确认有UTF-8 BOM,后续验证从第4字节开始 -
buf[0]==0xFF && buf[1]==0xFE或buf[0]==0xFE && buf[1]==0xFF→ 是UTF-16,直接排除UTF-8 - 读不满3字节(如空文件或仅1–2字节)→ 按“非UTF-8”保守处理,不强行猜测
逐字节状态机验证,必须转unsigned char
char在多数平台是有符号的,遇到0x80–0xFF会变成负数,导致b & 0xE0等位运算结果错误。所有字节必须显式转为unsigned char再判断。
- 单字节:
b <= 0x7F→ 合法ASCII,跳过 - 两字节起始:
(b & 0xE0) == 0xC0→ 要求后1字节存在且(s[i+1] & 0xC0) == 0x80;排除0xC0和0xC1(超长编码) - 三字节起始:
(b & 0xF0) == 0xE0→ 要求后2字节均满足(c & 0xC0) == 0x80;且不能是0xED 0xA0开头(代理对禁止) - 四字节起始:
(b & 0xF8) == 0xF0→ 要求后3字节合法;排除0xF5–0xFF开头(超出U+10FFFF) - 任何
0x80–0xBF字节单独出现 → 孤立尾字节,非法
采样长度与边界情况必须考虑
全文件扫描代价高,实际只需验证前1–4 KB;但空字符串、嵌入\0、中途截断的多字节序列都算非法——这些在std::string里合法,但在UTF-8语义里不成立。
立即学习“C++免费学习笔记(深入)”;
- 建议读取文件前
min(file_size, 4096)字节进std::vector<unsigned char>再验证 - 遇到
\0不能停:UTF-8允许\0作为有效字符(U+0000),需继续检查后续字节是否构成合法序列 - 末尾剩余1–3字节不足以构成完整多字节序列(如
0xE0 0x80只有两个字节)→ 直接判非法 - 纯ASCII内容(全
0x00–0x7F)既是合法UTF-8也是合法GBK,此时无法区分,只能返回“UTF-8合规”而非“一定是UTF-8”
真正难的不是写状态机,而是记住哪些字节组合永远非法:比如0xC0和0xC1开头的序列、0xF5–0xFF开头的任意序列、以及任何续字节不在0x80–0xBF范围内的位置——这些点漏检一个,整个判断就失去意义。


















