数据在内存中的存储

一. 整数在内存中的存储

整数的2进制表示方法有三种,即原码、反码和补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位最
高位的一位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值⼀律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
注意:数据在内存存储的时候,是二进制,但是在VS的内存窗口上展示的时候是16进制

二.大小端字节序和字节序判断

什么是大小端?
其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储,下面是具体的概念:
大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。
小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。

例题精讲

练习一

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)--百度笔试题
int check_sys()
{
	int a = 1;
	return (*(char*)&a);//小端返回1,大端返回0
}

int main()
{
	if (check_sys() == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

练习二

C语言中整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整 型提升。
原码的整型提升:改变类型直接写出新的原码即可
补码的整型提升:
有符号整型提升是按照变量的数据类型的符号位来提升的
无符号整型提升,高位补0
%d - 以十进制的形式打印有符号的整型

char a = -1  整型提升至 整型  其次存储

原码 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

反码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

截断补码并存储

        1 1 1 1 1 1 1 1 - a 

由于以十进制打印有符号的整型 

所以首先整型提升至整型 

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1                                                  1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  取反                                  原码 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1  加1                                     => -1        

signed char b = -1 整型提升至 有符号整型  其次存储

原码 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

反码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

截断补码并存储

        1 1 1 1 1 1 1 1 - b

由于以十进制打印有符号的整型 

所以首先整型提升至有符号整型 

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1                                                  1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  取反                                  原码 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1  加1                                    打印十进制有符号的整型 

  => -1        

unsigned char c = -1

-1先整型提升至有符号整型  

原码 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

反码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

截断补码并存储

        1 1 1 1 1 1 1 1 - c

由于以十进制打印有符号的整型 

所以首先整型提升至无符号整型 

补码 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

由于打印十进制有符号的整型

反码 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

原码 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

  => 255

练习三

-128只有补码 1000 0000

先整形提升至 有符号整型 

补码 11111111111111111111111110000000

截断存储 10000000 - a

由于打印无符号的整型 

所以首先整型提升至有符号整型 

补码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0

由于打印无符号的整型

反码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0

原码 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0

  => 4294967168

128先整型提升至 无符号整型

原码 00000000000000000000000010000000

截断存储  10000000 -a

由于打印无符号整型

所以首先整型提升 有符号整型

补码 11111111111111111111111110000000

由于打印无符号整型

反码 11111111111111111111111110000000

原码 11111111111111111111111110000000

   => 4294967168       

练习四

char的取值范围是 -128~127

当 i=128,-1-128=-129,就会越界了

a[128]=-129;

-129先整型提升至 有符号整型

原码 10000000 00000000 00000000 10000001

反码 111111111 111111111 111111111  011111110

补码 111111111 111111111 111111111  011111111

截断存储 01111111

补码 01111111

反码 01111111

原码 01111111

=> 127

所以当a[128]=-129时,存进去的是127代表的字符

以此类推

i=0  a[0]=-1

......

i=127 a[127]=-128

i=128 a[128]=127

......

i=255 a[255]=0='\0'

所以strlen(a)=255

练习五

unsigned char的取值范围 0~255

i=256的时候按道理会跳出循环

但事实并非如此

256首先整型提升至 无符号整型

原码 00000000 00000000 00000001 00000000

反码 00000000 00000000 00000001 00000000

补码 00000000 00000000 00000001 00000000

截断存储 00000000

补码 00000000

反码 00000000

原码 00000000

   => 0

所以当i=256时存进去的是0

所以会陷入死循环

这里按道理i=-1的时候会跳出循环

但事实并非如此

-1

原码 10000000 00000000 00000000 00000001

反码 111111111 111111111 111111111 111111110

补码 111111111 111111111 111111111 111111111

存储

        111111111 111111111 111111111 111111111

补码 111111111 111111111 111111111 111111111

反码 111111111 111111111 111111111 111111111

原码 111111111 111111111 111111111 111111111

打印无符号整型

    =>4294967295

......

依次类推

向下递减

无限循环

补充练习

&a是整个数组的地址  &a+1的地址大小是a[3]后面一个的地址,但代表一块16字节的地址

int * 将&a+1转换为 单独四字节的地址

ptr1[-1]==*(ptr1-1)==*(&a[3]+1-1)==*(&a[3])==a[3]==4

a=&a[0]  (int) a将地址转为10进制,然后再加1

0x0012ff40,1244992,加一,1244993,

将1244993转为16进制为0x0012ff41

实际上跳过了一个字节

int型有四个字节

跳过一个字节访问到的就是 00 00 00 02,由于是小端存储模式

输出的话就是 20 00 00 00

注意点:这是在32位情况下运行的,64位情况下地址是8个字节,用int会发生截断

应选择用 longlong

int* ptr2 = (int*)((long long)a + 1)
提醒: X64 - 64位环境下,指针的大小是8个字节 X86 - 32位环境下,指针的大小是4个字节

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

常见的浮点数:3.14159、1E10等,浮点数家族包括: floatdoublelong double 类型。
浮点数表示的范围:float.h中定义
整型类型的取值范围:limist.h中定义
根据国际标准IEEE(电气和电子工程协会)754;任意一个二进制浮点数V可以表示成下面的形式:
对于32位的浮点数,最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

有效数字M的具体存储

前面说过, 1M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目 的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保 存24位有效数字。

指数E的具体存储

首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

四.浮点数读取的过程

正常情况的读取

指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1
(正负取决于符号位s)

E全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
(正负取决于符号位s)

E全为1

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

例题

int n=9;易得出9的补码
补码:00000000000000000000000000001001
printf("n的值为:%d\n", n); 易得出结果为9
0 00000000 00000000000000000001001
第一个代表符号 为+
00000000 读取结果 1-127=-126
00000000000000000001001 读取结果 0.00000000000000000001001
答案就是:0.00000000000000000001001*2^(-126)
%f默认打印6位小数  结果就是0.000000
*pFloat = 9.0;1001.0
(-1)^0 * 1.001*2^3
S=0
M=1.001
E=3(以3+127=130的二进制存入)
01000001000100000000000000000000
printf("num的值为:%d\n", n);的结果就是上面直接读取的结果1091567616
printf("*pFloat的值为:%f\n", *pFloat);%f默认打印6位小数就是9.000000
浮点数没有原码反码补码之说
只有一个将其存储的码,并且可以直接读取展示

感谢您的阅读!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

你好,赵志伟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值