C语言数据在内存中的存储:补码、大小端与浮点数

C语言数据在内存中的存储

1. 整数类型与补码

整数类型包括 char、short、int、long、long long,并分为有符号和无符号版本。具体宽度由实现决定,可通过 sizeof 与 <limits.h> 查询。

计算机通常使用补码保存有符号整数:正数补码与原码相同;负数由原码符号位外取反后加1得到。

1
2
signed char a = -1;   // 常见8位表示为11111111
unsigned char b = -1; // 转换后通常为255

无符号整数按模 2^N 运算。有符号整数溢出属于未定义行为。

2. 整型提升

小于 int 的整数类型参与表达式时,通常先提升为 int 或 unsigned int。

1
2
3
char a = 3;
char b = 127;
int result = a + b;

分析表达式时不能只看变量声明,还要考虑整型提升和通常算术转换。

3. 大小端字节序

对于 0x11223344:

  • 大端:低地址存高有效字节 11 22 33 44;
  • 小端:低地址存低有效字节 44 33 22 11。

判断程序:

1
2
3
4
5
int is_little_endian(void)
{
unsigned int value = 1;
return *(const unsigned char *)&value == 1;
}

网络协议通常使用大端字节序,网络编程中可用 htons/htonl 和 ntohs/ntohl 转换。

4. 浮点数存储

现代平台通常采用 IEEE 754。单精度浮点数常由三部分组成:

  • S:符号位;
  • E:带偏置的指数;
  • M:有效数字的小数部分。

其基本形式可理解为:

1
(-1)^S × M × 2^E

浮点数不能精确表示所有十进制小数,例如 0.1 的二进制表示无限循环。因此不能总用 == 判断计算结果。

1
2
3
4
#include <math.h>

if (fabs(a - b) < 1e-9)
printf("近似相等\n");

5. 类型解释与别名

同一组字节被不同类型解释,会产生完全不同的数值。不要通过不兼容类型指针随意读取对象,这可能违反严格别名规则。需要检查对象表示时,使用 unsigned char * 或 memcpy 更稳妥。

1
2
3
float value = 1.0f;
unsigned char bytes[sizeof value];
memcpy(bytes, &value, sizeof value);

6. 总结

底层存储问题要区分“数值”“对象类型”和“字节表示”。补码解释整数,大小端决定多字节排列,IEEE 754 描述浮点编码;跨平台通信和二进制文件尤其需要明确这些差异。