【C】剖析数据在内存中的存储

情境引入:

先来看看下面的代码,你们觉得运行的结果会是怎么样呢?

#include<stdio.h>
int main()
{
	int a = 5;
	printf("a的值为:%d\n", a);

	float b = (float)a;
	printf("b的值为:%f\n", b);

	float* c = (float*)&a;
	printf("c的值为:%f \n", *c);

	*c = 5.0;
	printf("a的值为:%d \n", a);
	printf("a的值为:%f \n", *c);



	return 0;
}

   我还没学数据存储的时候,我认为的答案是5  ,  5.0, 5.0 ,5  , 5.0      

但答案实际上是:

是不是很纳闷为什么是这个结果?接下来就给你解密原理。

整形在内存中的存储

        先来了解一下最基本的三个概念吧:

原码:原码是指一个二进制数左边加上符号位后所得到的码,且当二进制数大于0时,符号位为0;二进制数小于0时,符号位为1;二进制数等于0时,符号位可以为0或1(+0/-0)

反码:正数的反码是原码,负数是将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:正数的反码是原码,负数反码+1得到补码。        

        总所周知,计算机只能存二进制数。那么整数要想在计算机中存储,肯定是要转化成二进制存储。

        那么直接在内存里面存储原码不就可以了吗?

        但设计的时候还要考虑运算问题和符号位的处理问题,如果用原码,符号位要单独拿出来处理。增加设计的复杂度。而且原码和补码的相互转换的,它们的运算过程也是相同的。

int a = 3;
int b = -5;

// 3
//原码     00000000 00000000 00000000 0000 0011
//反码     00000000 00000000 00000000 0000 0011
//补码     00000000 00000000 00000000 0000 0011
//-5
//原码     10000000 00000000 00000000 0000 0101
//反码     11111111 11111111 11111111 1111 1010
//补码     11111111 11111111 11111111 1111 1011
//补码取反 10000000 00000000 00000000 0000 0100
//补码取反加一  10000000 00000000 00000000 0000 0101
//

//3 + (-5) = -2
//两个的原码相加
//00000000 00000000 00000000 0000 0011   3
//10000000 00000000 00000000 0000 0101   -5
//10000000 00000000 00000000 0000 1000   -8 != -2,所以原码不可以对符号位进行统一处理

//两个的补码相加
//00000000 00000000 00000000 0000 0011   3  
//11111111 11111111 11111111 1111 1011   -5
//11111111 11111111 11111111 1111 1110   结果的补码
//10000000 00000000 00000000 0000 0001   结果的补码取反
//10000000 00000000 00000000 0000 0010   结果的原码 -2 ,所以原码可以对符号位进行统一处理

   那么在整形表达式中,是使用的原码还是补码进行计算的呢?

int main()
{
	int i = 20;
	unsigned int j = -10;
	printf("%d\n", i + j);
	return 0;
}

//使用的是原码的话
//20
// 00000000 00000000 00000000 00010100
//-10
// 10000000 00000000 00000000 00001010
// 10000000 00000000 00000000 00011110 相加,为-30

//使用的是补码的话
//20
// 00000000 00000000 00000000 00010100
//-10
// 11111111 11111111 11111111 11110110
//100000000 00000000 00000000 00001010 最前面的1越界,所以直接丢掉,转化为原码为10

总结:

  • 对于整形来说:数据在内存中存放的是数据的补码
  • 整形表达式计算使用的实际上是补码
  • 打印和看到的都是原码

浮点型在内存中的存储

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

  • (-1)^S * M * 2^E
  • (-1)^S 表示符号位,当S=0,V为正数; 当S=1时,V为负数
  • M表示有效数字,大于等于1,小于2
  • 2^E表述指数位

举个例子吧

比如十进制数13.5,转化为二进制就是1101.1 也就是1.1011x2^3

那么按照上面的形式,S = 0    M = 1101.1   E = 3

IEEE 754规定:

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

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

存储模型和上面类似,这里就不在画模型图了。

IEEE754对有效数字M和指数E,还有一些特别的规定。

前面说过,M大于等于1,M小于2.也就是说,M可以写成1.xxxxxx的形式,其中xxxxxxx表示小数部分。

IEEE754 规定,在计算机内部保存M时,默认这个数的第一位总是1,所以1就可以直接被省略,只保存后面的xxxxxx部分。

比如,保存1.01时,只保存01,等到读取数据的时候,再把第一位的1加上去。这样做的目的,是节省一位有效数字。以32位的浮点数为例,留给M的只有23位

但将第一位的省去之后,就可以保存24位有效数字(存放的23位加被省去的1位)。

不同于M,指数E的情况是比较复杂的。

首先要明确一点,E是一个无符号整数(unsigned int)

那么就是说E只能表示成非负数,并且范围是0~255。但是在科学计数法中,E显然是可以表示成负数的。

所以IEEE 754规定,存入内存时E的真实值必须加一个偏移量,对于8位的E,这个偏移量是127(也就是二进制里的七个1)

同理对于11位的E,这个偏移量是1023。

  

 上图是八位E是有符号数的取值范围

显然,偏移量实际上就是有符号取值的最大正数。

然后,指数E从内存中取出还可以分为三种情况:

E不全为0或不全为1

这时,浮点数就用以下的规则表示,即指数E的计算值减去偏移量,得到其真实值,再将有效数字M前加上第一位的1.

eg:

0.25的二进制形式为0.01.由于规定正整数部分必须为1,即将小数点右移两位,

则为1.00*2^(-2),其阶码为-2+127 = 125 表示为 0111 1101,而尾数1.00去掉整数部分为00,补齐0到23位00000000 00000000 0000000,那么二进制形式表示为

0 01111101 00000000000000000000000

 E全为0

这时,浮点数的指数等于1-偏移量,即为真实值

有效数字不再加上第一位的1,而是还原为0.xxxxxxx的小数。这样做是为了表示 ±0,以及接近于0很小的数字。

E全为1 

这时,如果有效数字M全为0,表示±无穷大;

浮点数是无法精确保存的,只能说是精确到了多少位。 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值