1. 问题现场:一个看似简单的位运算Bug
那天下午,我正在解析一个工业设备的通信协议。按照文档说明,需要从字节流中取出两个相邻字节,拼合成一个16位有符号整数(Int16),然后分别提取高字节和低字节进行后续处理。代码逻辑看起来非常简单:
csharp复制byte high = 0x80; // 十六进制80,二进制10000000
byte low = 0x42; // 十六进制42,二进制01000010
// 将两个字节拼合成16位有符号整数
short value = (short)((high << 8) | low); // 结果为0x8042,即-32702(补码表示)
// 提取高字节和低字节
int highByte = value >> 8; // 期望得到0x80(128)
int lowByte = value & 0xFF; // 期望得到0x42(66)
lowByte的结果完全符合预期,得到了0x42。但highByte的结果却让我大跌眼镜——不是预期的0x80(128),而是0xFFFFFF80(-128)。这完全违背了我的直觉:我只是简单地将数值右移了8位,高位不是应该补零吗?
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 底层原理剖析:三个关键概念的相互作用
2.1 补码表示法的本质
计算机中使用补码(Two's Complement)表示有符号整数,这个大家都知道。但容易被忽视的是:同一个数值在不同位宽下,补码的二进制表示形式完全不同。
以-1为例:
- 8位:
11111111(0xFF) - 16位:
11111111 11111111(0xFFFF) - 32位:
11111111 11111111 11111111 11111111(0xFFFFFFFF)
这些不同位宽的表示实际上都代表同一个数值-1。高位的1不是"多余的数据",而是补码系统为了在更宽的容器中正确表示这个负数而必须填充的内容。
2.2 隐式的符号扩展(Sign Extension)
当一个小位宽的有符号数需要放入大位宽的容器时,编译器会自动进行符号扩展:用原始数值的最高位(符号位)填充所有新增的高位。
例如:
- 正数:Int16的
0x0042→ Int32的`0x
