要了解整型在内存中的存储方式,我们首先要了解几个概念:原码、反码、补码
计算机中的有符号数有三种表示方法,即原码、反码和补码。
三种表示方法均由符号位和数值位组成,符号位为0表示正数,符号位为1表示负数,但数值位的三种表示方法各不相同。
- 原码:直接将数字按照正负数的形式翻译成二进制即可。
- 反码:将原码符号位不变,其他位依次按位取反即可得到反码。
- 补码:反码加一即可得到补码。
(注:对于正数来说,它们的原码、反码和补码均相同。)
- 对于整型来说:数据存放内存中其实存放的是补码。
这是为什么呢❓❓❓
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器),此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
让我们看看数据在内存中的存储:

因为 20 是正数,所以它的原码、反码和补码相同,于是 20 的补码转换为十六进制应该是 0x00 00 00 14,而 -10 的补码我们就需要进行一些运算才能得到。

所以 -10 的补码转换为十六进制为 0xff ff ff f6,但是在内存中我们看到的 a 是14 00 00 00 存放的,b 也是 f6 ff ff ff 存放的,为什么和我们计算出来的结果是相反的呢❓❓❓
其实这就涉及到了大小端的问题了!
Ⅱ. 大小端字节序
**大小端(Endianness)**问题是计算机系统中的一个重要概念,用于描述不同的数据类型在内存中的存储方式和字节顺序。
在计算机中,数据存储的最小单位是字节**(Byte),每个字节通常包含 8 个比特(Bit)**。对于多字节类型的数据(如整数、浮点数等),它们在内存中的存储顺序可以分为两种不同的方式:
- 大端字节序(Big Endian):数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。例如,十六进制数值 0x12345678 在大端字节序中的存储顺序是 12 34 56 78。
- 小端字节序(Little Endian):数据的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。例如,十六进制数值 0x12345678 在小端字节序中的存储顺序是 78 56 34 12。

而我们上面出现的问题其实就对应这里的小端存储,这表示我们当前的机器就是小端存储模式的。(也有大端存储模式的机器)
大端字节序和小端字节序的区别在于数据的高位和低位的存储顺序,它们的应用场景和优缺点也有所不同。其中,大端字节序通常用于网络协议和跨平台数据交换,小端字节序通常用于x86和ARM等处理器架构的硬件和操作系统中。
需要注意的是,不同的编程语言和操作系统对于大小端的支持和处理方式可能有所差异,因此在进行跨平台数据交换和格式转换时需要特别注意大小端的问题,以确保数据的正确性和可移植性。
除此之外,要区分开和位段的关系:位段是一个字节里面的位关系,而字节序是不同字节之间的序列关系。

Ⅲ. 浮点型在内存中的存储
在引入知识之前,先来看一个案例,就知道了解浮点型数据存储的重要性与必要性。
举个例子:
#include<stdio.h>
int main()
{
int num = 9;
float *pnum = (float *)#//强制转换类型
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pnum);
*pnum = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pnum);
return 0;
} 一般情况下我们都会认为 *pnum 打印的就是 n 的值,只不过是以浮点型打印出来,所以是 9.000000 啊,然后下面的 n 又因为 *pnum 的修改,加之因为 %d 输出,所以还是 9 啊,但是结果不是,打印出来的结果如下图所示:

结果与我们想的有很大的偏差。。。。。那就有必要让我们来了解浮点型数据的存储啦!
首先,根据上面的实例,我们可以发现**整型和浮点型数据的存储方法是不一样的!**
那么浮点型数据是怎么存储的呢❓❓❓
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示为下面的形式:
- 表示符号位:
- 当 S=0 时,V 为正数
- 当 S=1 时,V 为负数
- M 表示有效数字,大于等于1,小于2。
- 表示指数位。

IEEE 754规定: 对于32位的浮点数,最高的1位为符号位S,接着的8位是指数E,剩下的23位为有效数字M。

对于64位的浮点数,最高的1位位符号位S,接着的11位是指数E,剩下的52位为有效数字M。

关于M:
前面说过1<=M<2,所以M总是可以写成1.xxxxxx的形式,其中xxxxxx为小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的xxxxxx部分。比如保存1.01时,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省一位有效数字。关于E:
首先E为一个无符号整数(unsigned int),这意味着,如果E为8位,它的取值范围为0 ~2 55。如果E为11位,它的取值范围为0 ~ 2047。但是我们知道,科学计数法中的E是可以为负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如210,的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。此外,指数E从内存中取出还可以分成三种情况。
-
E不全为0且不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值加上127(或1023),得到真实值,再将有效数字M前加上第一位的1。

-
E为全0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
-
E为全1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。
所以我们再对刚才的代码加以解释:
1.首先我们先把n的补码写出来,因为n是正数,所以它的原反补相同。
2.按照上面的方法,把n的补码分解后发现它的E为全0,所以相当于一个无穷小的数字,所以此时*p为0.000000……
3.将*p改成9.0后按照上面的方法,先改成二进制 1001.1,然后写成 (-1)^0 * 1.001 * 2^3,最后按照格式变成补码,存到内存中去,此时补码为01000001000100000000000000000000,而n为整型数据,所以先取出这个补码,又因为其为正数,所以直接将补码转为十进制%d输出: