计算机只认识 0 和 1,那它是如何表示负数的?科学家们为此设计了三种编码方案,依次改进,最终解决了这个问题。
2.3
计算机只认识 0 和 1,那它是如何表示负数的?科学家们为此设计了三种编码方案,依次改进,最终解决了这个问题。
符号位约定:最高位(最左边)表示正负,0 代表正数,1 代表负数,其余位存储数值。以 8 位(1 字节)为例:
| 1 | 0 0000101 → +5(符号位为 0,表示正数) |
| 2 | 1 0000101 → -5(符号位为 1,表示负数) |
| ── 原码(错误)────────────────── | |
| 0 0000101 (+5) | |
| + 1 0000101 (-5) | |
| ────────── | |
| 1 0001010 = -10,完全不对!✗ | |
| ── 反码(接近但有瑕疵)─────────── | |
| 0 0000101 (+5 的反码) | |
| + 1 1111010 (-5 的反码) | |
| ────────── | |
| 1 1111111 = -0,还是不对 △ | |
| ── 补码(正确!)──────────────── | |
| 0 0000101 (+5 的补码) | |
| + 1 1111011 (-5 的补码) | |
| ────────── | |
| 1 0 0000000 最高位进位溢出,自动丢弃 | |
| 0 0000000 = 0 完全正确!✓ |
| 十进制 | 原码 | 反码 | 补码 |
|---|---|---|---|
| +5 | 0000 0101 | 0000 0101 | 0000 0101 |
| -5 | 1000 0101 | 1111 1010 | 1111 1011 |
| +0 | 0000 0000 | 0000 0000 | 0000 0000 |
| -0 | 1000 0000 | 1111 1111 | (不存在!) |
| -128 | 无法表示 | 无法表示 | 1000 0000 |
| -1 | 1000 0001 | 1111 1110 | 1111 1111 |
| +127 | 0111 1111 | 0111 1111 | 0111 1111 |
1000 0000 被用来表示 −128。8 位补码能表示 256 个数:−128~−1(128 个)加上 0~127(128 个),比原码多表示一个数。前面讨论的补码都是针对 1 个字节(8 位)展开的,但 int 占 4 个字节、long long 占 8 个字节——这些"大于 1 字节"的数据,在内存里到底是怎么摆放的?这就引出了两个新概念:低位字节与高位字节,以及数据摆放顺序的约定——字节序(大端 / 小端)。
以十六进制数 0x12345678 这个 int 为例,把它拆成 4 个字节:12、34、56、78。数值上越"重要"(越靠左、代表的数值越大)的字节叫高位字节(这里是 12),越靠右、数值贡献越小的叫低位字节(这里是 78)——这和十进制里"千位是高位、个位是低位"是同一个道理。
问题来了:内存地址是从小到大排列的一条线(见 6.1 节数组的内存布局),这 4 个字节该按什么顺序放进连续的地址里?"高位字节放在前面(小地址)"还是"低位字节放在前面(小地址)",这是完全可以自行约定的两种方案,也确实都有 CPU 在用:
__builtin_bswap32 这个函数真正的用武之地:它能快速把一个数的字节顺序整个翻转过来,实现大小端互转。记不住哪个是"大端"哪个是"小端"?可以这样理解名字的由来:"端"指的是数值的末端(最低位那一端):
| 1 | int n = 1; // 内存中只有最低位字节是 1,其余字节是 0 |
| 2 | char* p = (char*)&n; // 取 n 的地址,强制按单字节方式解读 |
| 3 | |
| 4 | if (*p == 1) |
| 5 | cout << "小端:最低地址存的是低位字节 1"; |
| 6 | else |
| 7 | cout << "大端:最低地址存的是高位字节 0"; |
n 的值是 1,在内存中只有最低位字节是 0x01,其余 3 个字节都是 0x00。把 int* 强制转换成 char* 再取第一个字节(也就是最低地址那个字节),如果读到的是 1,说明这台机器把"低位字节"放在了"低地址"——也就是小端;如果读到 0,则说明高位字节在最低地址,是大端。这是判断本机字节序最经典的写法。cin/cout、变量赋值、算术运算这些正常方式操作数据,字节序完全是透明的,编译器和 CPU 会自动处理好一切。只有当你需要"跳过语言的抽象、直接摸到内存里的每一个字节"时——比如网络编程、跨平台的二进制文件读写、或者上面这种指针强转的技巧——字节序才会成为一个需要认真考虑的问题。