深入理解C语言:数据在内存中的存储(整数、大小端、浮点数)
1. 整数在内存中的存储在C语言中整数类型如int、char、short、long在内存中是以二进制的补码形式存储的。理解这一点对于处理位运算、类型转换和内存操作至关重要。原码、反码、补码原码直接将数值按照正负号转换成二进制。最高位为符号位0正1负。反码正数的反码与原码相同负数的反码是原码符号位不变其余位取反。补码正数的补码与原码相同负数的补码是其反码加1。计算机使用补码存储整数因为补码可以将减法运算统一为加法运算简化了CPU的设计。代码示例查看整数的内存表示#include stdio.h int main() { // 定义两个整数变量分别存储正数和负数 int a 10; // 十进制10正数 int b -10; // 十进制-10负数 unsigned char *p; // 声明一个无符号字符指针用于按字节访问内存 // 打印变量的十进制值 printf(a %d\n, a); printf(b %d\n, b); // 查看a在内存中的每个字节十六进制 printf(a在内存中的字节从低地址到高地址); // 将整型变量a的地址强制转换为unsigned char*类型这样p就指向a的第一个字节最低地址 p (unsigned char *)a; // 循环遍历a的每一个字节sizeof(a)返回int类型占用的字节数通常是4 for (int i 0; i sizeof(a); i) { // %02x 表示以两位十六进制输出不足两位前面补0 // p[i] 等价于 *(p i)即访问第i个字节的内容 printf(%02x , p[i]); } printf(\n); // 查看b在内存中的每个字节十六进制 printf(b在内存中的字节从低地址到高地址); // 重新让p指向b的起始地址准备读取b的字节 p (unsigned char *)b; for (int i 0; i sizeof(b); i) { printf(%02x , p[i]); } printf(\n); return 0; }运行结果在小端模式下可能如下a 10 b -10 a在内存中的字节从低地址到高地址0a 00 00 00 b在内存中的字节从低地址到高地址f6 ff ff ff解释0a 00 00 00是10的补码小端存储低位在前。f6 ff ff ff是-10的补码。2. 大小端字节序和字节序判断大小端Endianness是指多字节数据在内存中存储的字节顺序。小端模式Little Endian数据的低位字节存储在内存的低地址处高位字节存储在高地址处。x86、ARM通常采用小端。大端模式Big Endian数据的高位字节存储在内存的低地址处低位字节存储在高地址处。网络字节序、PowerPC等采用大端。例如一个4字节的整数0x12345678小端存储地址从低到高78 56 34 12大端存储地址从低到高12 34 56 78代码示例判断当前系统的字节序#include stdio.h // 方法1使用联合体union判断字节序 int check_endian1() { // 定义一个联合体包含一个int成员和一个char成员 // 联合体的所有成员共享同一块内存空间大小由最大成员决定 union { int i; // 4字节整数 char c; // 1字节字符 } u; u.i 1; // 将整数1赋值给联合体的int成员 // 由于联合体共享内存u.c访问的是u.i的第一个字节最低地址字节 // 如果这个字节的值是1说明低位字节存储在低地址是小端模式 // 如果这个字节的值是0说明高位字节存储在低地址是大端模式 return u.c 1; } // 方法2使用指针判断字节序 int check_endian2() { int num 1; // 定义一个整数1 // 将int类型变量的地址强制转换为char*类型指针 // 这样可以通过指针按字节访问内存 char *p (char *)num; // 解引用指针获取num的第一个字节最低地址字节的值 // 原理与方法1相同如果第一个字节是1则为小端如果是0则为大端 return *p 1; } int main() { // 调用判断函数根据返回值输出系统字节序 if (check_endian1()) { printf(当前系统为小端模式Little Endian\n); } else { printf(当前系统为大端模式Big Endian\n); } // 直观展示一个整数的字节存储顺序 int x 0x12345678; // 定义一个十六进制整数便于观察字节顺序 // 将x的地址转换为unsigned char*用于按字节访问 unsigned char *ptr (unsigned char *)x; printf(整数 0x%x 在内存中的字节顺序从低地址到高地址\n, x); // 循环遍历x的每一个字节 for (int i 0; i sizeof(x); i) { // 输出每个字节的十六进制表示 // ptr[i] 访问第i个字节i0对应最低地址字节 printf(%02x , ptr[i]); } printf(\n); return 0; }运行结果在常见x86系统上当前系统为小端模式Little Endian 整数 0x12345678 在内存中的字节顺序从低地址到高地址 78 56 34 123. 浮点数在内存中的存储C语言中的浮点数float、double遵循IEEE 754标准在内存中存储。以单精度浮点数float32位为例1位符号位S0表示正数1表示负数。8位指数位E表示2的幂次采用移码表示实际指数 E - 127。23位尾数位M表示有效数字的小数部分隐含整数部分为1除非是特殊值。浮点数的值计算公式为(-1)^S * 1.M * 2^(E-127)代码示例剖析浮点数的内存布局#include stdio.h #include stdint.h // 联合体用于将float的位模式解释为整数便于按位查看 typedef union { float f; uint32_t u; } float_union; void print_float_bits(float val) { float_union fu; fu.f val; uint32_t bits fu.u; // 提取符号位、指数位、尾数位 uint32_t sign (bits 31) 0x1; uint32_t exponent (bits 23) 0xFF; uint32_t mantissa bits 0x7FFFFF; // 23位 printf(浮点数: %f\n, val); printf(十六进制: 0x%08x\n, bits); printf(符号位 S: %u (%s)\n, sign, sign ? 负数 : 正数); printf(指数位 E: %u (实际指数 %d)\n, exponent, (int)exponent - 127); printf(尾数位 M: 0x%06x\n, mantissa); printf(二进制: ); for (int i 31; i 0; i--) { printf(%d, (bits i) 1); if (i 31 || i 23) printf( ); // 分隔符 } printf(\n\n); } int main() { float f1 6.5f; float f2 -3.75f; float f3 0.0f; print_float_bits(f1); print_float_bits(f2); print_float_bits(f3); // 验证一个特殊值NaN非数 float_union nan_val; nan_val.u 0x7fc00000; // 典型的NaN表示 printf(NaN 示例: 0x%08x 对应的浮点数值: %f\n, nan_val.u, nan_val.f); return 0; }运行结果示例浮点数: 6.500000 十六进制: 0x40d00000 符号位 S: 0 (正数) 指数位 E: 129 (实际指数 2) 尾数位 M: 0x500000 二进制: 0 10000001 10100000000000000000000 浮点数: -3.750000 十六进制: 0xc0700000 符号位 S: 1 (负数) 指数位 E: 128 (实际指数 1) 尾数位 M: 0x700000 二进制: 1 10000000 11100000000000000000000 浮点数: 0.000000 十六进制: 0x00000000 符号位 S: 0 (正数) 指数位 E: 0 (实际指数 -127) 尾数位 M: 0x000000 二进制: 0 00000000 00000000000000000000000 NaN 示例: 0x7fc00000 对应的浮点数值: nan三种存储方式对比下表横向对比了整数存储补码、大小端字节序和浮点数存储IEEE 754的核心要点对比维度整数存储补码大小端字节序浮点数存储IEEE 754存储格式二进制补码形式正数与原码相同负数为反码加1字节排列顺序小端低位在前或大端高位在前符号位(S) 指数位(E移码) 尾数位(M)主要目的/优势统一加减法运算简化CPU设计解决0的表示唯一性问题定义多字节数据在内存中的字节顺序不同架构有不同约定科学计数法的二进制实现平衡表示范围与精度支持特殊值NaN、无穷典型应用场景所有整数运算、位操作、类型转换、内存直接读写网络通信需统一字节序、跨平台数据交换、文件格式解析科学计算、图形处理、金融计算、任何需要小数或极大/极小数值的场景需要注意的陷阱溢出问题、有符号/无符号转换时的符号扩展、移位操作的未定义行为跨平台/跨网络时字节序转换htonl/ntohl、直接内存拷贝可能导致数据错乱精度损失如0.1无法精确表示、比较浮点数应使用误差范围、特殊值NaN、无穷处理总结整数存储使用补码便于运算。大小端影响多字节数据的字节顺序编程时需要注意尤其是网络通信和文件读写。浮点数存储遵循IEEE 754标准理解其内存布局有助于避免精度问题和进行底层优化。