我们都知道计算机用补码存整数,但printf出来的却是原码。这中间发生了什么?本文带你走一遍从代码到内存再到输出的完整流程。
一、先记住这一句
整数在内存中永远存补码,但输出时会根据格式控制符转成原码显示。
为什么?
-
存补码:计算机只用加法器就能处理减法
-
输出原码:人类只看懂带符号位的原始形式
二、完整流程
| 存入内存 | 原始数值 → 写出原码 → 按变量类型调整位数 → 转补码 → 存入内存 |
| 输出屏幕 | 读取补码 → 按格式符调整位数 → 转原码 → 转十进制 → 显示 |
看不懂没关系,后面每步都会拆开细讲,看完再回来看这张表就懂了。
三、整数存入计算机(原码 → 补码)
当你定义一个变量并赋值时(如 char a = -5 或 long long b = 100),计算机按以下两步处理:
① 写出原码
直接按定义的类型写出原码。
| char a = -5 | 10000101(8位) |
| short b = -5 | 10000000 00000101(16位) |
| int c = -5 | 10000000 00000000 00000000 00000101(32位) |
| long long d = -5 | 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101(64位) |
规则:最高位是符号位(0正1负),后面跟数值绝对值的二进制。
② 转成补码(最终存储)
规则:
-
正数/无符号数:补码 = 原码(不变)
-
有符号负数:补码 = 原码的符号位不变,数值位取反,再加1
示例:
| ①原码 | 10000101 | 10000000 00000000 00000000 00000101 |
| ②取反 | 11111010 | 11111111 11111111 11111111 11111010 |
| ③加1 | 11111011 | 11111111 11111111 11111111 11111011 |
| 内存 | 11111011 | 11111111 11111111 11111111 11111011 |
四、计算机输出整数(补码 → 原码)
当你用printf输出时(如 printf("%d", a)),计算机反向操作:
① 读取补码
从内存中取出之前存的补码:11111011(以char a = -5为例)
② 按格式控制符调整位数
看你是用%d、%u、%hd还是%hhd,按目标类型调整位数:
情况A:格式符位数 > 变量位数 → 符号扩展
-
有符号数:高位补符号位(正数补0,负数补1)
-
无符号数:高位补0
情况B:格式符位数 < 变量位数 → 截取低位
情况C:格式符位数 = 变量位数 → 不变
示例:char a = -5 用 %d 输出
| char补码 | 8位 | 11111011 |
| 符号扩展(负数补1) | 32位 | 11111111 11111111 11111111 11111011 |
③ 还原成原码
| 正数/无符号数 | 补码直接就是原码,无需转换 |
| 有符号负数 | 补码 → 减1 → 数值位取反(符号位不变) |
示例:%d输出-5
| 补码(规整后) | 11111111 11111111 11111111 11111011 |
| 减1 | 11111111 11111111 11111111 11111010 |
| 取反(符号位不变) | 10000000 00000000 00000000 00000101 |
| 原码结果 | = -5 |
④ 转十进制输出
原码 10000000 00000000 00000000 00000101 = -5,屏幕上显示 -5
五、示例:同一份内存,不同的输出结果
示例1:%d vs %u
char a = -5; // 内存中存的是补码:11111011
printf("%d\\n", a); // 输出:-5
printf("%u\\n", a); // 输出:4294967291
-
用%d输出:11111011 → 符号扩展成32位11111111 11111111 11111111 11111011 → 减1取反得原码10000000 00000000 00000000 00000101 = -5
-
用%u输出:11111011 → 符号扩展成32位11111111 11111111 11111111 11111011 → 直接当无符号数解读 = 4294967291
核心:同一内存,不同格式符,输出不同。
示例2:%hu 输出
char a = -5;
printf("%hu\\n", a); // 65531
处理流程:
整型提升:11111011 → 符号扩展成32位 11111111 11111111 11111111 11111011
截取低16位:11111111 11111011
当无符号数解读:65531
示例3:-128的特殊情况
char a = -128;
printf("%hu\\n", a); // 65408
-128的补码是 10000000
处理流程:
整型提升:10000000 → 符号扩展成32位 11111111 11111111 11111111 10000000
截取低16位:11111111 10000000
当无符号数解读:65408
六、核心总结
6.1.存储与输出的完整流程
存储时:按定义类型写原码 → 负数取反加一转补码 → 存内存
输出时:取补码 → 按格式符做整数提升/截断 → 负数减一取反转原码 → 显示
6.2.三个核心概念
计算机只认补码:内存里永远存的是补码
人类只看原码:输出时一定要还原成原码
格式控制符决定最终看到什么:同一块内存,用%d和%u看完全不同
6.3.整数提升规则
-
小类型(char、short)做运算或传参时,先转成int
-
有符号按符号位扩展,无符号补0
七、练习题
分析下面代码的输出:
char a = -1;
printf("%d %u %hu %hhu\\n", a, a, a, a);
答案:
-
%d:-1
-
%u:4294967295
-
%hu:65535
-
%hhu:255
你能按流程推出来吗?
结语
现在你明白了吗?
-
存补码:是为了让计算机好计算
-
输出原码:是为了让人类能看懂
-
中间经历了整数提升 + 符号扩展/截断 + 原反补转换
下次再看到奇怪的输出结果,不妨按这个流程走一遍,保证能看懂!



