为什么 Shift JIS 字节序列 0x82 0x51 能被 CP932 正常解码?
0x82 0x51 是有效的 Shift JIS 字节序列,在 CP932 中对应全角数字 2,即 U+FF12。校验规则的问题在于:后续字节的范围并不由前导字节的奇偶性决定。CP932 映射表列出了 0x8251 → 0xFF12。CP932 映射表
给出的完整字节序列应解码为 ラングリッサー2。末尾是全角 2,需要与 ASCII 数字 2 区分。
为什么按奇偶性校验会出错?
问题中的规则混淆了 Shift JIS 前导字节和 JIS X 0208 字符表行号的奇偶性。
在传统的 JIS X 0208 到 Shift JIS 转换中,一个 Shift JIS 前导字节承载相邻两行字符。这两行分别使用不同的后续字节范围:
| JIS X 0208 行号 | Shift JIS 后续字节范围 |
|---|---|
| 奇数行 | 0x40–0x7E 或 0x80–0x9E |
| 偶数行 | 0x9F–0xFC |
这里的奇偶性指 JIS 行号。一个前导字节可以搭配这两部分范围内的字节,不能根据前导字节本身的奇偶性来选择范围。
例如,前导字节 0x82 对应 JIS X 0208 的第 3、4 行。0x82 0x51 使用第 3 行,符合编码规则。
从双字节结构看,后续字节的候选范围为:
0x40–0x7E 或 0x80–0xFC
0x7F 被排除。WHATWG 的 Shift_JIS 解码算法也按上述范围处理后续字节,没有按前导字节的奇偶性分支。该规范面向 Web 兼容实现,具体扩展映射仍应与目标解码器区分。Shift_JIS 解码算法
这组字节实际表示什么?
按字符边界分组,解码结果如下:
| 字节 | 字符 |
|---|---|
83 89 | ラ |
83 93 | ン |
83 4F | グ |
83 8A | リ |
83 62 | ッ |
83 54 | サ |
81 5B | ー |
82 51 | 2 |
前 14 个字节解码为 ラングリッサー,最后两个字节是全角数字 2。这些映射均可在 CP932 映射表中查到,0x82 0x51 的成功解码不需要用扩展字符或错误替换来解释。CP932 字符映射
如何修改校验和解码逻辑?
先确定要支持的编码。如果目标是复现 Windows code page 932 的行为,可以使用 CP932 解码器并启用 MB_ERR_INVALID_CHARS。遇到无效输入时,函数会失败,便于处理错误。MultiByteToWideChar 文档
下面的 C++ 示例使用完整的 16 字节输入:
#include <windows.h>
#include <string>
#include <system_error>
std::wstring decodeTitle()
{
const unsigned char bytes[] = {
0x83, 0x89, 0x83, 0x93, 0x83, 0x4F, 0x83, 0x8A,
0x83, 0x62, 0x83, 0x54, 0x81, 0x5B, 0x82, 0x51
};
const char* input = reinterpret_cast<const char*>(bytes);
const int inputSize = static_cast<int>(sizeof(bytes));
const int size = MultiByteToWideChar(
932, MB_ERR_INVALID_CHARS,
input, inputSize, nullptr, 0
);
if (size == 0) {
throw std::system_error(
static_cast<int>(GetLastError()),
std::system_category()
);
}
std::wstring result(size, L'\0');
if (MultiByteToWideChar(
932, MB_ERR_INVALID_CHARS,
input, inputSize, &result[0], size) == 0) {
throw std::system_error(
static_cast<int>(GetLastError()),
std::system_category()
);
}
return result;
}
数组没有以 NUL 结尾,因此这里显式传入字节数。使用正数长度时,MultiByteToWideChar 不会自动在转换结果末尾添加 NUL;示例由 std::wstring 管理字符串存储。长度与终止符规则
如果保留自写校验器,需要检查字符边界、前导字节、后续字节,以及该组合在目标字符表中是否有定义。字节范围正确只能说明结构符合候选规则,不能保证每个组合都有字符映射。
判断 ASCII 和 Shift JIS 时还要注意什么?
- 这组输入包含多个大于
0x7F的字节,超出了 ASCII 范围,可以排除纯 ASCII。 - 不能逐字节独立判断字符。
0x51单独出现时可以表示 ASCII 字符Q,但在这里它是0x82的后续字节。 - 控制字符与编码错误要分开处理。
0x00–0x1F和0x7F在 CP932 中有控制字符映射。游戏标题字段可以按格式要求拒绝这些字符,但应将其作为字段内容限制。CP932 控制字符映射 - 需要明确扩展版本和实现。不同实现对扩展区域的处理可能不同。CP932 解码成功,不能直接证明数据只使用了严格 Shift JIS 字符集;不过本例中的
0x82 0x51属于基础字符映射。
如果候选编码只有 ASCII 和 CP932,可以先检查是否全部为 ASCII 范围内的字节,再对其余输入尝试严格 CP932 解码。如果候选还包含其他编码,解码成功只能证明兼容性,仍需结合镜像字段规范和文本含义判断。
备注:内容仅供参考。
