一、前言
在C语言编程中,我们经常使用整数和浮点数,但很少有人真正了解它们在内存中是如何存储的。理解这些底层细节,能帮助我们写出更健壮的代码,也能更好地理解一些“奇怪”的现象,比如为什么浮点数运算会有精度问题。
本文将深入探讨整数和浮点数在内存中的存储方式。
二、整数的存储
1. 原码、反码、补码
整数在内存中以补码形式存储。要理解补码,首先要知道原码和反码。
类型 正数 负数
原码 符号位为0,其余为数值位 符号位为1,其余为数值位
反码 同原码 符号位不变,数值位按位取反
补码 同原码 反码+1
示例: 以8位二进制为例,+1和-1的表示:
text
+1 原码:0000 0001
+1 反码:0000 0001
+1 补码:0000 0001
-1 原码:1000 0001
-1 反码:1111 1110
-1 补码:1111 1111
2. 为什么使用补码?
使用补码的好处是:可以将减法统一为加法运算。CPU只需要设计加法器,就能完成加法和减法。
例如:1 + (-1) = 0
text
0000 0001 (1的补码)
+ 1111 1111 (-1的补码)
= 0000 0000 (0,进位丢弃)
3. 有符号和无符号
有符号整数:最高位为符号位(0正1负),取值范围 -2^(n-1) ~ 2^(n-1)-1
无符号整数:全部位都是数值位,取值范围 0 ~ 2^n - 1
类型 字节数 取值范围
char 1 -128 ~ 127
unsigned char 1 0 ~ 255
int 4 -2^31 ~ 2^31-1
unsigned int 4 0 ~ 2^32-1
三、浮点数的存储
1. IEEE 754 标准
浮点数遵循 IEEE 754 标准,分为三部分存储:
text
浮点数 = (-1)^S × M × 2^E
S(Sign):符号位,0表示正数,1表示负数
E(Exponent):指数位,存储时加上偏移量
M(Mantissa):尾数位,存储小数点后的有效数字
2. float 类型(32位)
text
| 1位符号位S | 8位指数位E | 23位尾数位M |
偏移量:127
实际指数 = E - 127
3. double 类型(64位)
text
| 1位符号位S | 11位指数位E | 52位尾数位M |
偏移量:1023
实际指数 = E - 1023
4. 存储示例
以 float f = 5.5 为例:
5.5 转二进制:101.1
科学计数法:1.011 × 2^2
符号位 S = 0
指数 E = 2 + 127 = 129 = 1000 0001
尾数 M = 01100000000000000000000(隐藏最高位1)
最终存储:0 10000001 01100000000000000000000
5. 浮点数的精度问题
重要: 并非所有浮点数都能精确保存!
例如 0.1 转换为二进制是无限循环小数:
text
0.1 = 0.00011001100110011...(循环)
计算机只能截断存储,因此 0.1 + 0.2 ≠ 0.3 是常见现象。
6. 特殊值
情况 符号位S 指数位E 尾数位M
0 0 全0 全0
无穷大 0/1 全1 全0
NaN 0/1 全1 非0
四、整型和浮点型存储对比
对比项 整型 浮点型
存储标准 补码 IEEE 754
表示范围 固定范围 更大范围
精度 精确 部分值不精确
存储结构 直接存储数值 分为S、E、M三部分
五、验证代码示例
1. 查看整数的补码表示
c
#include <stdio.h>
int main() {
int a = -1;
unsigned int *p = (unsigned int*)&a;
printf("%#x\n", *p); // 输出:0xffffffff
return 0;
}
2. 查看浮点数的十六进制存储
c
#include <stdio.h>
int main() {
float f = 5.5;
unsigned int *p = (unsigned int*)&f;
printf("%#x\n", *p); // 输出:0x40b00000
return 0;
}
3. 浮点数精度问题演示
c
#include <stdio.h>
int main() {
float f = 0.1f;
double d = 0.1;
printf("float 0.1 : %.20f\n", f);
printf("double 0.1: %.20f\n", d);
if (0.1 + 0.2 == 0.3) {
printf("相等\n");
} else {
printf("不相等\n");
}
return 0;
}
六、常见面试题
1. 判断大小端
c
int check_endian() {
int a = 1;
char *p = (char*)&a;
return *p; // 小端返回1,大端返回0
}
2. 为什么浮点数不能直接判等?
因为浮点数存储不精确,应该用差值比较:
c
#define EPS 1e-6
if (fabs(a - b) < EPS) {
// 认为相等
}
七、总结
整数用补码存储,方便统一加减法运算
浮点数用 IEEE 754 标准存储,由符号位、指数位、尾数位三部分组成
浮点数存在精度问题,不能精确保存所有小数
理解底层存储,有助于解决溢出、截断、精度损失等问题
八、参考资料
IEEE 754 标准文档
《深入理解计算机系统》第2章
C语言标准(C99/C11)