合法UTF-8需按字节模式验证:单字节0xxxxxxx、双字节110xxxxx 10xxxxxx、三字节1110xxxx 10xxxxxx 10xxxxxx、四字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;非法字节(如孤立10xxxxxx、11111xxx等)应跳过,推荐用状态机逐字节过滤。

如何判断一个字节序列是否为合法UTF-8
UTF-8不是逐字节可验证的,必须按字节模式组合识别:单字节0xxxxxxx、双字节110xxxxx 10xxxxxx、三字节1110xxxx 10xxxxxx 10xxxxxx、四字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。任意位置出现10xxxxxx但前面没匹配到对应起始字节(如110xxxxx),或出现11111xxx/10xxxxxx开头的孤立字节,都算非法。
别用std::string::erase()边遍历边删——迭代器失效风险高;也别依赖std::codecvt_utf8(C++17已弃用且实现常有bug)。
用状态机逐字节扫描并过滤非法字节
写一个轻量状态机,输入字节流,输出合法UTF-8子序列。状态包括:start(等待新字符)、expect1(期待1个后续字节)、expect2(期待2个)、expect3(期待3个)。遇到非法字节直接跳过,不计入输出。
- 遇到
0xxxxxxx→ 输出该字节,状态回start - 遇到
110xxxxx→ 记录需再读1字节,状态变expect1 - 遇到
1110xxxx→ 状态变expect2 - 遇到
11110xxx→ 状态变expect3 - 遇到
10xxxxxx→ 仅当处于expect*状态时才接受并推进计数;否则丢弃 - 遇到
11111xxx、11110000~11110100之外的四字节首字节、超范围码点(如U+D800~U+DFFF)也应丢弃
示例核心逻辑:
立即学习“C++免费学习笔记(深入)”;
C++ Code Review Master
下载
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
std::string filter_utf8(const std::string& s) {
std::string out;
int expecting = 0; // 0: start, 1/2/3: need that many trailing bytes
for (unsigned char c : s) {
if (expecting == 0) {
if ((c & 0x80) == 0) { // 0xxxxxxx
out += c;
} else if ((c & 0xE0) == 0xC0) { // 110xxxxx
if (c >= 0xC2 && c <= 0xDF) { // exclude overlong & C0/C1
out += c;
expecting = 1;
}
} else if ((c & 0xF0) == 0xE0) { // 1110xxxx
if (c >= 0xE0 && c <= 0xEF) {
out += c;
expecting = 2;
}
} else if ((c & 0xF8) == 0xF0) { // 11110xxx
if (c >= 0xF0 && c <= 0xF4) {
out += c;
expecting = 3;
}
}
// else: invalid starter → skip
} else {
if ((c & 0xC0) == 0x80) { // 10xxxxxx
out += c;
--expecting;
} else { // wrong trailing byte → reset and skip
expecting = 0;
}
}
}
return out;
}
为什么不能只删0xFF或0x00这类“明显坏字节”
单个字节0xFF或0xC0本身不违法UTF-8语法(0xC0是过长编码的起始,但仍是有效字节),真正问题在于上下文组合。比如À 中0xC0后跟0x20(非10xxxxxx)才构成错误;而 是合法的NBSP。盲目删除某个字节会破坏后续合法序列。
常见误操作:
- 用
std::regex匹配"\x80-\xFF"然后删——这会干掉所有多字节字符的首字节 - 调用
iconv(..., "UTF-8", "ISO-8859-1", ...)再转回——可能静默替换或截断,不可控 - 依赖第三方库如
utf8cpp的is_valid()但只检查整体字符串——它返回false却不告诉你哪部分错,无法做局部清理
实际使用时要注意BOM和嵌入NUL
UTF-8 BOM()是合法字节序列,不应删除;但若原始字符串含C风格