整数和浮点数在内存中存储的区别

发布时间:2026/5/21 15:09:20

整数和浮点数在内存中存储的区别 整数和浮点数在内存中的存储详解在编程中我们经常与各种数据类型打交道比如整数、浮点数。但你有没有想过这些数据在计算机内存中是如何存储的呢为什么有时候我们打印出来的结果和预期不一样今天我们就来深入探讨整数和浮点数在内存中的存储机制揭开它们的神秘面纱。1. 整数在内存中的存储1.1 原码、反码、补码在计算机中整数是以二进制形式存储的。对于有符号整数通常有三种表示方法原码、反码和补码。它们都包含符号位和数值位最高位为符号位0 表示正1 表示负其余位为数值位。原码直接将数值按照正负数的形式翻译成二进制。例如5 的原码是00000101假设用8位表示-5 的原码是10000101。反码正数的反码与原码相同负数的反码是符号位不变其余位按位取反。例如-5 的反码是11111010。补码正数的补码与原码相同负数的补码是反码加1。例如-5 的补码是11111011。在计算机系统中整数在内存中实际上是以补码的形式存储的。这是为什么呢主要有以下几个原因符号位和数值位统一处理补码可以将符号位和数值位一起参与运算无需特殊处理。加法和减法统一处理CPU只有加法器使用补码可以将减法运算转化为加法运算例如A - B等价于A (-B)的补码。0的表示唯一原码中0和-0的表示不同而补码中0只有一种表示避免了歧义。1.2 大小端字节序当我们查看内存中整数的存储时有时会发现数据的字节顺序和我们想象的不太一样。比如在调试器中查看变量int a 0x11223344;可能会看到内存中存储的顺序是44 33 22 11从低地址到高地址。这就是所谓的大小端字节序问题。什么是大小端大端模式数据的高位字节保存在内存的低地址处数据的低位字节保存在内存的高地址处。小端模式数据的低位字节保存在内存的低地址处数据的高位字节保存在内存的高地址处。例如对于0x11223344假设内存地址从低到高大端存储11 22 33 44小端存储44 33 22 11我们常用的 x86 架构是小端模式而 ARM 架构通常也是小端但可以配置。网络协议中常采用大端模式也称为网络字节序。为什么会有大小端这是因为计算机系统以字节为单位编址而一个多字节数据如 int、short需要占用多个连续的字节。如何安排这些字节的顺序不同的硬件设计者有不同的选择。小端模式有利于数据类型转换如将 int 强制转换为 char 时可以直接取低地址的字节大端模式则更符合人类的阅读习惯从左到右高位到低位。如何判断当前机器的字节序我们可以通过一个小程序来判断#include stdio.h int check_sys() { int i 1; return *(char*)i; // 取i的第一个字节 } int main() { int ret check_sys(); if (ret 1) printf(小端\n); else printf(大端\n); return 0; }原理int i 1在内存中的十六进制表示为0x00000001。如果是小端低地址存放的是01所以(char*)i指向的字节值为1如果是大端低地址存放的是00返回0。还可以使用联合体union来判断int check_sys() { union { int i; char c; } un; un.i 1; return un.c; // 小端返回1大端返回0 }1.3 整型提升与截断当我们将一个超出范围的值赋给一个变量时会发生截断。例如char a -1; // -1 是 int 类型其补码为 11111111 11111111 11111111 11111111 // 赋给 char只保留低8位11111111即 -1 的补码。当我们以%d打印char类型时会发生整型提升将 char 提升为 int对于有符号 char高位补符号位。所以char a -1提升后仍是-1。练习分析以下代码的输出。#include stdio.h int main() { char a -128; printf(%u\n, a); // 输出什么 return 0; }char a -128-128 的补码是100000008位。当以%u打印时a 先整型提升为 int由于 char 是有符号的高位补1得到11111111 11111111 11111111 10000000这个数作为无符号整数解释是一个很大的数4294967168。类似地char a 128也会得到同样的结果因为 128 的二进制是10000000在 char 范围内-128~127会溢出实际存储的也是10000000即 -128 的补码。1.4 综合练习再看一个有趣的例子#include stdio.h #include string.h int main() { char a[1000]; int i; for (i 0; i 1000; i) { a[i] -1 - i; } printf(%d\n, strlen(a)); // 输出什么 return 0; }这里a[i] -1 - i当 i 从 0 开始a[0] -1a[1] -2... 直到 a[127] -128然后 a[128] -129但 -129 超出了 char 的范围-128~127会发生截断。实际上-129 在 char 中的补码是多少我们可以模拟一下-1 的补码是 11111111-2 是 11111110...-128 是 10000000-129 应该是 10000000 - 1 01111111即 127。所以 a[128] 127a[129] 126... 直到 a[255] 0a[256] -1等等。strlen 遇到第一个 0 就停止所以从 a[0] 到 a[255] 共有 255 个非零字符注意 a[255] 0我们来计算当 i 255 时-1 - 255 -256-256 的补码考虑 char 8位是 0因为 -256 的补码低8位为 0。所以 a[255] 0因此 strlen 返回 255。但注意 a[128] 开始为正数直到 a[254] 为 1a[255] 为 0。所以实际 strlen 为 255。另一个经典例子#include stdio.h unsigned char i 0; int main() { for (i 0; i 255; i) { printf(hello world\n); } return 0; }这是一个死循环因为 i 是 unsigned char取值范围 0~255当 i 255 时加 1 后变成 0永远满足 i 255所以无限打印。最后一个复杂例子x86小端环境int main() { int a[4] {1, 2, 3, 4}; int *ptr1 (int *)(a 1); int *ptr2 (int *)((int)a 1); printf(%x, %x, ptr1[-1], *ptr2); return 0; }a是整个数组的地址类型为int (*)[4]a 1跳过整个数组16字节指向数组之后的位置。然后强制转换为int*所以ptr1指向数组末尾的下一个位置。ptr1[-1]相当于*(ptr1 - 1)即数组最后一个元素也就是 4。所以第一个输出是 4十六进制为 4。(int)a将数组名 a 强制转换为 int 类型即数组首元素的地址值。加 1 后地址值增加 1 个字节。然后强制转换为int*所以ptr2指向从数组首地址偏移 1 字节的位置。在小端模式下数组 a 在内存中的布局假设地址从低到高a[0] 1 的十六进制0x00000001存储为 01 00 00 00a[1] 202 00 00 00a[2] 303 00 00 00a[3] 404 00 00 00从首地址偏移 1 字节后取 4 个字节得到的内容是00 00 00 02注意小端低地址是 00高地址是 02组合起来是 0x02000000即 33554432十六进制为 2000000。所以输出4, 2000000。2. 浮点数在内存中的存储浮点数在内存中的存储与整数完全不同它遵循 IEEE 754 标准。常见的浮点数类型有 float32位和 double64位。2.1 浮点数的表示根据 IEEE 754任意一个二进制浮点数 V 可以表示为V(−1)S×M×2EV(−1)S×M×2ES符号位0 为正1 为负。M有效数字尾数范围是 [1, 2) 或 [0, 1)。E指数位。对于 32 位 float最高 1 位符号位 S接下来 8 位指数 E最后 23 位有效数字 M对于 64 位 double最高 1 位符号位 S接下来 11 位指数 E最后 52 位有效数字 M存储细节有效数字 M在二进制中M 总是可以写成 1.xxxxx 的形式规格化数其中 1 是隐含的因此实际存储时只存储小数部分 xxxxx这样可以多保存一位精度。例如对于 5.0二进制为 101.0科学计数法为 1.01×2^2M 的小数部分为 01存储时只存 01后面补 0 至 23 位。指数 EE 是一个无符号整数但实际指数可能为负。因此 IEEE 754 规定存储时需要加上一个偏移量bias对于 8 位 E偏移量为 127对于 11 位 E偏移量为 1023例如2^10 的 E 为 10存储时存 10127137即 10001001。特殊情形E 不全为 0 且不全为 1规格化数此时指数真实值为 E - biasM 隐含 1。E 全为 0非规格化数此时指数真实值为 1 - bias或 1-127M 不再隐含 1而是 0.xxxx用于表示 0 或接近 0 的数。E 全为 1如果 M 全为 0表示无穷大±∞如果 M 非全 0表示 NaNNot a Number。2.2 浮点数存储示例让我们通过一个经典例子来理解浮点数的存储与读取差异#include stdio.h int main() { int n 9; float *pFloat (float *)n; printf(n 的值为%d\n, n); // 9 printf(*pFloat 的值为%f\n, *pFloat); // 0.000000 *pFloat 9.0; printf(n 的值为%d\n, n); // 1091567616 printf(*pFloat 的值为%f\n, *pFloat); // 9.000000 return 0; }为什么会出现这样的结果第一步n 9在内存中的存储假设为小端9 的二进制32位00000000 00000000 00000000 00001001当用浮点数视角解释时按照 float 格式拆分S 0最高位E 000000008 位M 0000000000000000000100123 位即尾数部分此时 E 全为 0属于非规格化数指数真实值 1 - 127 -126M 不再加 1而是 0.00000000000000000001001所以数值为V(−1)0×0.00000000000000000001001×2−126≈1.001×2−146V(−1)0×0.00000000000000000001001×2−126≈1.001×2−146这是一个非常小的数接近于 0所以用%f打印为 0.000000。第二步将*pFloat赋值为 9.0即内存中写入浮点数 9.0 的表示。9.0 的二进制1001.0 1.001 × 2^3所以S 0E 3 127 130二进制 10000010M 001 后面补 0 至 23 位00100000000000000000000因此 9.0 的浮点表示32位为0 10000010 00100000000000000000000即十六进制0x41100000注意小端存储为00 00 10 41实际上按字节小端是00 00 10 41但整数解释时按 32 位整数读取值为 0x41100000 1091567616。所以打印n得到 1091567616。2.3 浮点数的取值范围与精度float 的指数范围是 -126 到 127规格化数加上非规格化数可以表示非常接近 0 的数。有效数字 23 位大约相当于 7 位十进制有效数字。double 则提供更高的精度。注意浮点数在内存中的存储是近似值有些十进制小数无法精确表示为二进制小数比如 1.2在内存中会有舍入误差这是浮点数运算时需要注意的。3. 总结整数在内存中以补码形式存储便于加减法统一处理。不同机器可能有大小端字节序之分这影响了多字节数据的排列顺序。浮点数遵循 IEEE 754 标准由符号位、指数和尾数构成存储时有规格化和非规格化等特殊情况。整数和浮点数的存储方式完全不同因此用整数视角解释浮点数内存或反之会得到意想不到的结果。理解数据在内存中的存储有助于我们编写更健壮的代码避免因数据溢出、类型转换等引发的 bug。希望本文能帮助你揭开数据存储的神秘面纱让你在编程路上更进一步

相关新闻