13.【C语言进阶】数据的存储
数据基本类型
为什么会有不同的类型,这些类型有内存大小上的差异,那么他们还有什么差异呢?
- 在内存中开辟空间的大小
- 读取内存空间方式的差异
char //字符类型
short //短整型
int //整型
long //长整型
long long //更长整型
float // 单精度浮点型
double // 双精度浮点型
除此以外,在整型中同一种类型的数据还分为有符号和无符号型.
整型中:
char
signed char
unsigned char
short
signed short
unsigned short
int
signed int
unsigned int
long
signed long
unsigned long
浮点数类型:
float
double
当然,还有指针类型,大致相同不再赘述。
整型在内存中的存储
既然不同类型在内存中需要不同的空间,那么它们如何在内存存储的呢?
int main()
{
int a = 10;
int b = 5;
printf("%d %d", a, b);
return 0;
}
0x010FFC88是a在内存中的地址,在这里我们可以看到:
0a 00 00 00 分别是四个字节,而0a就是我们找到的那一个地址,即我们通常所说的a的首地址。
在32位机器上int整型的大小为32个bit,即四个字节,这里是使用的16进制数字来表示的,每四个二进制位可以转换为一个十六进制位(每四个二进制位的权重与一个十六进制位的权重相同,所以这里可以使用8个十六进制数字来表示a的值。
首先我们先要知道:
二进制在计算机中都是以补码的形式存储的。
为什么会存在补码?
首先,如果单纯的使用原码进行计算,由于符号位的存在,那么必定时会存在很多问题,那有没有一种二进制序列,可以不考虑符号位的存在直接进行运算的呢?
此外,原码和补码的相互转化,都是取反加一并不需要额外的计算机硬件电路。
- 正数的原码和补码相同。
- 负数的补码和原码有着取反加一的关系。
比如 -1
原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
既然32bit是四个字节,而计算机中基本的内存单位也是一个字节,那么超过一个字节的数据在内存中是按何种顺序存储的呢?
首先我们要知道有高位和低位的区别,权重高的位称为高位,反之低位。
接下来我们就可以继续了。
大端存储:
存放低位的字节在高地址处,高位的字节在低地址处。
小端存储:
存放低位的字节在低地址处,高位的字节在高地址处。
注意:大小端仅针对C语言中的内置数据类型,以宏观的角度看整个结构体,是不存在所谓的高位和低位的也就不存在字节序的问题了,成员的存储遵循大小端存储模式。
也从另一个角度说明了对结构体的访问是不存在宏观对于整个结构的单独访问,而是对它的成员的访问和读取。
再来看这张图:
a == 10
十六进制:0000000a
可我们却看到好像数据在内存中不是这样存放的,可以看到0a也就是数据的低位字节是在低地址处的,它的高位字节是在内存中的高地址处的。
为什么会存在大小端存储
这是因为我们在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节是8bit。
另外,对于位数大于8位的处理器,例如16位或者32位处理器,由于寄存器的宽度大于一个字节,也就意味着在处理数据时如何安排多个字节的问题。
想测测自己目前所使用机器时如何存储数据的吗?
从二者的差异入手,对于1
补码:00000000000000000000000000000001
如果数大端存储:
那么在内存中应该是 00 00 00 01
而如果是小端存储应该是 01 00 00 00
那么我们就从此入手,看看在其首地址处的那个字节里保存的是什么,就可以解决相应问题。
#include<string.h>
int main()
{
int a = 1;
char* p = (char*)&a;
if (*p == 1)
printf("小端字节序\n");
else
printf("大端字节序\n");
printf("%d ", *p);
return 0;
}
浮点型存储规则
int main()
{
int n = 9;
float* pa = (float*)&n;
printf("%d\n", n);
printf("%f\n", *pa);
return 0;
}
同样的数据同样的字节数,为什么打印出来却不同呢?
其实,这是浮点数和整型在内存中的存储方式的差异导致的。
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E
- (-1)^S表示符号位,当S = 0时表示正数,当S= 1S时表示负数。
- M表示有效数字 大于等于1小于2
- 2^E表示指数位。
例如:十进制的5.0,写成二进制是101.0,类比十进制的科学计数法我们还可以写成1.01*22.
按照上面规定的格式我们就可以得到:
S = 0,M = 1.01,E= 2。
同上:
十进制的 -5.0,写成二进制为 -1.01 * 22 ,那么只有正负号改变了,仅仅将上面的S改为1,其他的不变就可以了。
既然在表示一个浮点数需要在内存中存储这三个数据,那么这究竟在内存中是如何存放的呢?
IEEE 754规定:
对于32位的浮点数,最高位的是符号位S,紧接着的8个位是指数E,剩下的23位存放的是数字的有效位。
而对于64位的浮点数,最高位仍然是符号位,不过指数位和有效数字位都所有增加,分别是11位和52位。
IEEE 754还对M和E有一些特殊规定:
前面说过,类比十进制的科学计数法,而我们都知道科学计数法的有效数字范围为[ 1,10),同样这里的有效数字M也必须满足 1 <= M <2,也就是说无论是什么样的数字,最终都可以写成1.xxxxxxxxx * 2 n这样的形式,因此为了提高我们的精度,在内存中,我们仅仅存放有效数字中小数点后面数字,在我们需要读取时,再将其加上去,这样就为可以存储的有效位数就由23位变为了24位。
例如:1.01 * 2 2.
我们在存储有效数字M的时候仅仅将 1.01 中的 01 存放,将1舍去。
64位浮点数同理。
至于指数E,情况稍微复杂
首先,E是一个无符号正数(unsigned int)
对于32 位浮点数来说,E有8位,也就意味着它的取值范围是0~255.
但是我们知道,这里的E是可以存放负数的,因此IEEE754规定,我们在内存中存入E时,必须将真实值加上一个中间数,对于8位的E,这个中间数是127,而对于64位的E这个中间值是1023.
也可以直接这样理解:
在32位浮点数中,指数的范围是 (0-127,255-127)。
例如 1.01 * 210的E是10,但我们想存到内存中时,必须先加上中间数127,也就是137,然后再将137的二进制序列存入到内存中。
E的存放还可以分为三种:
- E为不全为1或不全为0
这时是最一般的情况,我们先通过E在内存中的计算值减去127得到指数的真实值,然后再将有效数字M加上第一位的1。
例如:
浮点数 5.0 ->二进制可以表示为 1.01 * 102
-
指数部分先加上 中间数127 ,得到 129.
-
129 的二进制为 10000001.
-
有效数字为1.01,去掉正数部分为 01,补齐01到23位 01000000000000000000000
则其二进制序列可表示为:
0 10000001 01000000000000000000000
- E为全0
这是浮点数的指数的真实值就为 1-127或者1 - 1023.
而有效数字就不再加上首位的1,这样就可以表示无限接近于 0。
-
E全为 1
这时,如果有效数字M全为0,则表示无穷大。
int main()
{
int n = 9;
float* pa = (float*)&n;
printf("%f", *pa);
return 0;
}
那么我们来解决这个问题。
先来看 整型 9 的二进制序列:
00000000000000000000000000001001
我们取出它的地址后,以浮点数的存储方式去看待它,那么可以看成
0 00000000 0000000000000000001001
可以看到这里和我们的的第三种情况 E全为0相同,浮点数可以写成:
V=(-1)^0 × 0.00000000000000000001001×2(-126)=1.001×2(-146)
很显然这是一个无限接近于0的数字,所以用十进制数字表示就是0.000000.
问题解决了~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)