目录
前引
这篇文章是在我学习C语言后,对C语言的数据在内存中的存储的一点自己的认识和看法的总结。可能加深一下你对C语言数据的存储的认识。
数据类型详细介绍
首先我们要知道C语言的数据类型有两种,第一种是内置类型:int、long、short、char、float、double。第二种是:自定义类型:通过Typedef来自定义一个类型的名字。类型的意义在于:1、设置计算机分配的空间。2、在计算机中储存的样式(例如整形10和10.0)
整形在内存中的存储
整形家族分别有:int(4字节)、long(8字节)、short(2字节)、char(1字节)。加上前缀unsigned或者signed,来区分整形是分有符号的还是无符号的。(不加任何前缀,默认为有符号整形)
了解一下unsigned和signed的区别。他们叫做无符号和有符号。我们拿char举例,char占一个字节,8个位,有符号的char,第一位要做为符号位,即signed char的最大值为0111 1111。那么无符号位的第一位就可以做为大小,即unsigned char的最大值为1111 1111。总结:无符号位能表示的数字更大。
我们来通过char,来了解一下整形的某些规律。Char:分为unsigned char(0~255)和signed char(-128~127)。
整形的数字变化都是有这一些限制的,我们拿char举例。如果char达到了127,再加一,超出了范围,这时char的值就变为了-128(10000000若放在char内则被规定为-128)。具体转变情况如下图所示:
这个循环的圆圈,在int和longlong等类型中都和char的效果一样的,我们可以观察char来联想其他整形类型的数据,由于其他类似数据太大且与char类似,这里就不一一赘述。
若要查上述这些整形的范围可以在安装目录里找到VC——include——limits.h。下面我们来看一道例题来加深对整形存储的理解。
整形char在内存中储存的例题
求下列代码最终打印的值。
分析:首先观察a数组是char类型,所以a中存放的数字只有从-128~127。而我们后续的strlen(a)只有识别到\0或者数字0才能结束检测。我们观察for循环内部,a[0]=-1,a[1]=-2.....a数组从-1一直到-128,但是继续减一-129是不能储存在char类型的数组内的按照char数字的循环的-128继续减一就变成了127,然后继续-1直到变为0,循环又重新从-1开始。所以其实我们a数组内存放的元素是:-1~-128接着从127~0,再从1~-128直到循环1000个元素循环终止。所以我们用strlen去检测a,只要识别到第一个0,strlen就停止,最终打印的值是128+127=255
整形在内存中的存储:原码、反码、补码
有符号整形在计算机中有3中表示方式,即原码、反码、补码。(无符号数也有原码、反码、补码,只不过无符号数的原码、反码、补码相同)同理,正数的原码、反码、补码也都相同。如果是负数,则需要进行转换。
三种表示方式均有符号位和数值位两部分,符号位在数字二进制位最高位,都是用0表示正,1表示负。
原码:直接将数按照正负数的形式翻译成二进制就行。
反码:原码符号位不变,其他位按位取反(0变1,1变0)
补码:反码+1得到补码
注意:在内存储存的整数,都是存补码。正数的原码、反码、补码都相同。
为什么内存中的整数都存的是补码呢?
因为在计算机系统中,数值一律用补码来表示和存储。使用补码原因在于,若使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码可以相互转换,其运算过程是相同的,不需要额外硬件电路。
我们用-10来举例:
-10原码:1000 0000 0000 0000 0000 0000 0000 1010
-10反码:1111 1111 1111 1111 1111 1111 1111 0101
-10补码:1111 1111 1111 1111 1111 1111 1111 0110
-10的补码,写为十六进制则为:0x f f f f f f f 6。但是VS2013版本中内存中储存的16进制数字是倒着存的,上述0xff ff ff f6在内存中就存位0xf6 ff ff ff
具体情况入下图所示
为什么会倒着存储呢?这就引入了计算机中的大小端存储。
大小端字节序介绍及判断
什么是大端小端?
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。即正向存储(顺着)
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。即反向存储(逆着)
高低地址是按照计算机编码大小排序的。
什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的 每个地址单元都对应看一字节,一个字节为8bit,但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具本的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字5,那么必然存在着一个将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22低字节,对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中,小模式,刚好相反。我们常用的x86结构是小端模式,而KFIL C51则为大端模式。很多的ARM,DSP都为小模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
小端存储例题:如何判断一个编码器是大端还是小端存储
分析:我们自己知道VS2013编码器是小端储存,即16位的数字,倒着存储,11223344在内存中存为44332211,当我们对&a进强制类型转换为char*,那我们只能访问&a的一个字节,只能把第一个字节变为0。所以最后打印的结果为11223300。如果编码器为大端储存,则会把11223344改为00223344,我们只需要看最后打印的16进制数据的结果,就可以判断改编码器是大端存储还是小端存储。
浮点型在内存中的存储解析
介绍完整形在内存中的存储,我们来看一下浮点型的存储。
关于浮点型的在内存中的储存,我们先来看一组代码的结果:
如上图所示,可以明显看出来,第二个pFloat的值和第三个新n的值,这两个打印的结果是错误的,为什么错误呢?我们借此机会了解一下浮点型存储与整形存储的区别。
根据国际标准IEEE(电气电子工程协会)754标准定义,任意一个二进制浮点数a都可以表示成下面的形式:(注意M是二进制表示方式)
(-1)^ S * M * 2 ^ E
其中(-1)^S表示符号位,当S=0时,a为正数;当s=1时,a为负数
M表示有效数字,M大于等于1,小于2
2^E表示指数位。
我们拿上述图片中的9.0来举例,9.0改成二进制表达方式为1001.0,用我们上述标准可以写为:(-1)^ 0 * 1.001 * 2 ^ 3 ,其中S=0,M=1.001,E=3。
我们再来看一下小数的情况,我们拿十进制0.5来举例,它可以写为二进制0.1这里的0的权重是2^0=1;1的权重是2^-1即2/1=0.5,所以十进制的0.5写为2进制就是0.1。这里的E我们知道是-1但是储存时会加上127让其的变为正数。(因为E是无符号数,只能是正数,后续会详细介绍S、M、E的值的规则。)
浮点数的S M E在内存中的分配
对于单精度浮点数和双精度浮点数在内存中的上述S M E是如何分配的呢?具体情况如下图所示,第一张为32位浮点数,即float在内存中的存储。
下面这张图是64位浮点数,即double在内存中的存储。
IEEE 754对有效数字M和指数E,还有一些特别规定。
M的规定
前面说过,1<M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分(所以,M只表示小数部分)
IEEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。(还原时先抄M,剩余后面缺多少补0直至补到23位)至于指数E,情况就比较复杂。
E的规定
首先,E为一个无符号整数(unsigned int)这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数(保持E恒大于0),对于8位的E,这个中间数是127(所以E的范围就是-127~128);对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
情况1:E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。比如:0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位。则为1.0*2^(-1).其阶码为-1+127=126,表示为01111110 而尾数1.0去掉整数部分为0,补齐0到23位(加23个0),则其二进制表示形式为:
S E M
0 01111110 00000000000000000000000
情况2:E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数,这样做是为了表示±0,以及接近于0的很小的数字。
情况3:E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
在我们了解完上述知识之后,我们在重新对上述浮点数代码进行解析。
9.0用float格式写出其二进制为:0 10000010 00100000000000000000000
小结
通过上述介绍和解读,我们就了解了C语言的整形(int、char、long、short)和浮点型(float、double)在内存中的存储格式以及存储内容。了解了上述内容,我们就知道C语言一些数据类型溢出或者报错可能是什么情况,这将会加深我们对C语言的了解。