数据在内存中的的存储

1.数据类型介绍

内置类型

char , short , int , long , float , double , long long (C99)

long 类型的大小是 4 / 8 个字节,元素C语言规定 sizeof(long)>= sizeof(int)就行。在32位平台上,long为4个字节,在64位平台上,long位8个字节。

类型的意义

1.使用这个类型开辟内存空间的大小(内存大小决定了适用范围)

2.如何看待内存的视角。(数据存储到内存中和从内存中读取的方式)

类型的分类

整形家族

char : char , unsigned char , signed char。字符的本质是ASCII码值,是整型,所以划分到整型家族。 而char类型又有些特殊,他分成char ,unsigned char , signed char ,当我们用char定义一个字符时,它默认是unsigned char 还是signed char 是标准未定义的,取决于编译器的实现,在vs中默认是signed char。而其他整型家族的类型当没有明确写unsigned和signed时,都默认是signed有符号的整型。

short:unsigned short [ int ] , signed short [ int ]

int  : unsigned int , signed int 

long : unsigned long [ int ] , signed long [ int ]

long long : unsigned long long [ int ] , signed long long [ int ] 

为什么会有 unsigned 和 signed 的区别,生活中有些值是没有负数的,比如人的身高体重年龄,长度宽度等。而我们在之前也知道了有符号数在内存中存的是补码,最高位是符号位,不是有效位,只表示这个数是整数还是负数。当某一种数据只有正数没有负数时,就不需要符号位,这时候每一位都是有效位,都是有数值意义的,这样表示的范围也变大了。

浮点型家族

float         double

表示小数,float 精度低,存储的数据范围较小。double 精度高,存储的数据范围更大。

构造类型(自定义类型)

数组类型,结构体类型( struct ),枚举类型( enum ),联合类型( union )

指针类型

int * ,char * ,void * …………

空类型

void

无类型,通常用于函数返回类型(表示函数不会返回值),函数参数(表示函数不需要传任何参数)和指针类型。

当我们写下一个不需要参数的函数时,我们在调用这个函数的时候如果给他参数,他也会进入函数执行,但是当我们在函数定义的时候用void表示函数不需要参数,这时调用函数传参的话,虽然也会执行函数的内容,但是编译器会报一个警告。

2.整型在内存中的存储

在之前我们讲到了整数的二进制表示有原码、反码和补码三种,而存在内存中的是整数的补码,

我们在内存窗口中看到的是十六进制表示,但是在内存中实际存的还是二进制序列,转换成十六进制更方便我们观察。为什么存在内存中的是补码而不是反码和原码呢?这是因为,使用补码可以将数值域和符号位统一处理,同时,加法和减法也可以统一处理(cpu只有加法器),此外,补码和原码的相互转换,其运算过程是相同的,不需要额外的硬件电路。(由补码得到原码也可以先取反再加一,与原码到补码的过程是一样的)。 

3.大小端字节序介绍和判断

在上面的代码中,我们发现在内存中的数据好像是以字节为单位反着存的。

大小端介绍 ,以十六进制数 0x 11 22 33 44为例

大端字节序存储:把一个数据的高位字节的内容存放在低地址处,把低位字节的内容放在高地址处

小端字节序存储:把一个数据的高位字节的内容放在高地址处,把低位字节的内容放在低地址处。

大端和小端讨论的是字节的顺序,对于一个字节没有顺序可言,只有两个或以上个字节才有顺序的问题。

数据是大端存储还是小端存储不取决于编译器,而是取决于硬件。

我们如何判断我们的机器是大端存储还是小端存储呢?

一个很简单的方法就是存一个整型1,如何查看存进去的第一个字节的内容是什么,如果是1就是小端存储,如果是0就是大端存储。 我们将1的地址强制转换成char*类型就能看他的第一个字节了。

4.浮点型在内存中的存储

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

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

这里的 ^ 都是表示次方的意思,不是异或操作符。

(-1)^ S 表示符号位,当S=0,V为正数;S=1,V为负数。

M表示有效数字。取值为 1<=M<2

2^E表示指数位

举例来说,十进制的5.0,写成二进制的形式为:101.0(小数写成二进制分小数点前和小数点后来写,小数点后的位的权重可以参考整数部分,比如小数点后第一位的权重为2^-1,第二位为2^-2),当我们把101.0的小数点往左移两位,后面就要乘2^2,就可以写成  1.01 * 2 ^ 2  ,按照上面的科学计数法,S为0,M的值为 1.01 ,E的值为 2.

再比如9.5,写成二进制就是1001.1,小数点向左移三位,就可以写成 1.0011 * 2 ^ 3 ,这里的S为0,M为1.0011,E为3.

但是有些值是无法用二进制准确表示出来的,比如9.6,当你把它写成二进制的形式时你会发现不知道要小数点后多少位才能精确表示0.6的大小,而当位数太多时,后面的位可能存不下去,因为float只有四个字节的大小,也就是三十二个比特位,double也只有八个字节的大小,都有可能无法精确保存有些小数。

而知道了浮点数的表示方法后,我们就能大概知道浮点数在内存中只要存储S、M和E就能够表示浮点数。而IEEE754规定:

对于三十二位浮点数(float),最高的一位是符号位S,接着的八位存放指数E,低位的23位用来存放M。

对于六十四位浮点数(double),最高的一位是符号位S,接着的11位用来存放指数E,低位的52位为有效数字M、

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

前面说过M是大于等于1,小于二的数,所以M的小数点前的数一定是1,因此这个1可以舍去,只保存后面的小数部分,比如当M为1.01时,只保存01,等到读取的时候再把小数点前的1补上去,这样做能够节省一位有效数字,当我们保存小数点后二十三位时,加上小数点前面的1就相当于能表示24位有效数字。

至于指数E,情况就比较复杂。

首先E是一个无符号整数。如果E为八位,它的取值范围就是0~255,如果E为十一位,他的取值范围就是0~2047,但是我们知道科学计数法中的E是可以出现负数的,比如0.5的E就是-1,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数,对于八位的E,这个中间数是127,对于十一位的E,这个中间数是1023.就比如 0.5f 的E真实值为-1,要保存成-1+127,也就是126(01111110)。

比如存一个float类型的 5.5f ,写成二进制就是101.1,转换成科学计数法就是1.011*2^2 , S为0,M为1.011,E真实值为2,存入内存时,S为0,E为129(10000001),M存的是011,float存的 M 的位数为23位,位数不够的情况下后面补0,所以M存的是01100000000000000000000.所以5.5f在内存中存的是 0100 0000 1011 0000 0000 0000 0000 0000,转换成16进制就是0x 40 b0 00 00,而小端存储在内存中反着存,存的就是 00 00 b0 40。

这就是浮点数的存储的过程,存储的过程不是很难,难的是读取的过程。

读取时zhishuE从内存中取出还可以再分为三种情况:

1.E不全为0或不全为1

这时候E的计算值减去127(或1023)就得到E的真实值。再把M前面的1还原就能得到真实的浮点数的值(怎么存的就怎么取出来)。

2.E全为0

这时,规定指数E等于1-127(或1023)就是真实值,此时有效数字M不再加上前面的1,而是还原为0.xxxxxx的小数,这样做是为了表示正负0以及无限接近于0的很小的数字。(当E存进去的值为0时,说明真是指数很小,最大都是2^-127,当一个小于2的数乘以这个指数得到的也是一个无限接近0的数)

3.E全为1

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

  • 49
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值