【C语言-数据存储】数据在内存中是怎样存储的?

前言

调试观察内存的时候,总发现数据是倒着存的?

浮点数怎么和整形大不相同的感觉?

今天来揭下数据存储神秘的面纱


1.数据类型

回顾一下数据类型

  1. 整形:都有 signed & unsigned type

char - 字符类型
short - 短整形
int - 整形
long - 长整型
long long - 更长的整形

  1. 浮点数

float - 单精度浮点数
double - 双精度浮点数

  1. 构造类型

数组类型 - type array[ ]
结构体类型 - struct
枚举类型 - enum
联合类型 - union

  1. 指针类型:指针的类型决定了所指向的对象的类型,也决定了能访问多大内存空间

int* pi
char* pc
float* pf

  1. 空类型

void (无类型)

类型决定了:

  • 为此数据开辟的内存空间有多大
  • 编译器是如何看待这块内存空间

2. 整形在内存中的存储

*关于整形类型的数值范围:可以在“limits.h”中看到
在这里插入图片描述
奇怪,-10 怎么是个 " f6 ff ff ff " ?

2.1 原码、反码、补码

其实,对于整形的存储,有原码、反码、补码的概念

原码
直接将数值按照正负数的形式写成二进制

反码
符号位不变,对原码按位取反

补码
反码+1

  • 正整数的原反补相同

没事儿为什么要搞出个原反补?便于计算

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
一处理

同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程
是相同的
,不需要额外的硬件电路。

由上,可以知道内存中存储的是数据的补码

再倒回来看看上面的" f6 ff ff ff"

在这里插入图片描述
简单计算一下,就可以对应…

诶? 怎么 f6 的位置不对劲?

这里又牵扯到“大小端字节序”…

2.2 大小端字节序

我们已经知道:内存中,一个内存单元的大小为一个字节。而许多类型的数据所占空间都不止一个字节,因此产生一个问题:如何安排多个字节?

大端字节序(大端存储模式):地址由低到高,先存高位,后存低位
小端字节序(小端存储模式):地址由低到高,先存低位,后存高位

我用的 vs2019 编译器采用的就是 小端字节序 ,所以

地址由低到高,先存了 f6 ,再存 ff ff ff

来看一道百度面试题:

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)


int check_sys()
{
	int i = 1;
	//1:
	//00 00 00 01
	//01 00 00 00
	char* pi = (char*)&i;
	if (*pi)
		return 1;
	else 
		return 0;
}
int main()
{
	int ret = check_sys();
	if (ret)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

通过 char* 来达到只访问一个字节的目的,通过首个字节判断大小端


3. 浮点数在内存中的存储

*关于浮点数类型的数值范围,可以在“float.h”中看到

看看这个:

int main()
{
 	int n = 9;
 	float *pFloat = (float *)&n;
	printf("n的值为:%d\n",n);
 	printf("*pFloat的值为:%f\n",*pFloat);
 	*pFloat = 9.0;
	printf("num的值为:%d\n",n);
	printf("*pFloat的值为:%f\n",*pFloat);
 	return 0; 
 }

想想,应该是: 9 , 9.0 , 9 , 9.0

实际结果是什么呢?

在这里插入图片描述

怎么是这么个结果?

num 和 *pfloat 完全是一个数,怎么打印出来相差这么多?

对了,类型决定了编译器看待内存空间的视角!那原因肯定出在 整形和浮点型的解读规则不一样

必须了解浮点数存储规则才行…

3.1 浮点数存储规则

根据国际标准 IEEE(电气和电子工程协会)754,任意的二进制浮点数 V 都可以表示成同一种形式:

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

(-1)^S 代表符号位 ; M 代表有效数字 ; E 代表指数位

S 只占用1个比特位,当 S = 0,V为正数;反之为负数
M 占用23个比特位, 1 <= M < 2(有时也是0.xxxx)
E 占用8个比特位,E是 size_t 类型的

  • float存储模型
    在这里插入图片描述
  • double存储模型

也没什么区别,不过是

S(1bit) -> E(11bit) -> M(52bit)

举个例子:十进制的 5.0 ,二进制 101.0 是正的, 也是 (-1)^0 * 1.01 * 2^2
S=0 , M=1.01, E=2

3.2 M & E

3.2.1 有效数字 M

上面提到, 1 <= m < 2 ,也就是 M 都是写成 1.xxxxx 的形式,其中xxxxx表示小数部分

但在 IEEE 754 中规定,存储M的时候,总是舍去第一位的1(因为1.xxx中,1总是不变的),等到读取的时候再补上

这样做可以节省一位有效数字

3.2.2 指数 E

E作为 size_t(unsigned int)类型,实际存储要加上中间值127/1023来避免负数,当E为全0/全1时 也要单独讨论

  • 中间值
    小数的指数,可能为负,如0.5 = 0.1 = 1.0 * 2^-1,而且E又是unsigned int >=0 ,为了放得进去,我们通常加上一个中间值 127/1023 (8位的E/11位的E),读取的时候又减掉中间值
  1. 当E不为全0或不为全1

指数E的计算值(原E+中间值),减去中间值(127/1023)得到真实值 , 有效数字M前补上第一位的1

  1. 当E为全0

1-中间值 即为真实值 , 有效数字M 前 ,也不再补1 , 而是补 0

为什么?想想:原E + 中间值 = 0 ,即是说 原E = -127 ,1.xxxxxx * 2^-127 ,可以视作无穷小了 , 前面补上0 ,可以更好地表示无限接近0的数

  1. 当E为全1

如果 有效数字M 为全0 ,则表示±∞

了解了浮点数的存储规则,再回头分析前面的题目,才有下手的地方

int main()
{
 	int n = 9;
 	float *pFloat = (float *)&n;
	printf("n的值为:%d\n",n);//(1)
 	printf("*pFloat的值为:%f\n",*pFloat);//(2)
 	//0 00000000 0000000 00000000 00001001
	//S=0 M=0000000 00000000 00001001 E = -126
	//+ 0000000 00000000 00001001 * 2^-126
	//E为全0,非常接近于0 ,用十进制表示就是 0.000000
 	*pFloat = 9.0;
	printf("num的值为:%d\n",n);//(3)
	//浮点数n的二进制序列
	//0 10000010 0010000 00000000 00000000
	//S=0   E=3+127=130   M=001 (1暂时丢掉)
	//这个二进制序列以 "%d" 的格式打印 就是1091567616
	printf("*pFloat的值为:%f\n",*pFloat);//(4)
 	return 0; 
 }

在这里插入图片描述

分析
(1):以 “%d” 的形式打印一个十进制整形,类型和格式匹配,没毛病
(2):这里是把 9(int) 看成 float , 也就是说,编译器会把“整形9”的二进制序列当作浮点数的二进制序列分析
(3):把将 9.0 以浮点数规则存进去,再用"%d"格式打印
(4):类型和格式匹配,没毛病

可能误解的地方:整形9的 1001 怎么前面一堆0,而浮点9的(1)001 却在后面补0

  1. 整形9 1001 前面都是0 ,是因为它作为整形9的二进制序列本来就是这样,此时的序列不是我们通过浮点数存储规则分析出来的
  2. 浮点9的二进制序列,是我们自己分析出来的,而浮点9在后面补0,才能不影响数值
    1.001 * 2^3 才对,1.000000…0001*2^3 当然就不对了

补充:printf 打印的是原码哦,如果这里是 -9 ,打印时要转成原码打印


今天的分享就到这里,培根的blog,与你共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周杰偷奶茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值