C语言之深度剖析数据在内存中的存储

发布于:2023-01-11 ⋅ 阅读:(350) ⋅ 点赞:(0)

目录

一、数据类型的介绍

二、整型在内存中的存储

1、原码,反码,补码

  2、大、小端的存储模式

3、整型提升 

4、char类型的范围分析

5、好题详解 

二、浮点型在内存中的存储

1、浮点型在内存中的表示

 2、对于有效数字M与指数E的特殊规定

 3、好题详解


一、数据类型的介绍

在我的c语言第二篇博客中已经粗阅的介绍了数据类型以及它们所占的字节。

char        //字符数据类型     //1
short       //短整型              //2
int         //整形                    //4
long        //长整型              //4
longlong   //更长的整形     //8
float       //单精度浮点数     //4
double      //双精度浮点数   //8

 这是几种常见类型的表格:

二、整型在内存中的存储

1、原码,反码,补码

        要知道整型在计算机中的存储,首先要知道整型在计算机中的表示方法。整型在计算机中,有三种不同的表现方式,也就是原码,反码及补码,但整型是以补码的方式存储在内存中的。浮点数不存在原码,反码和补码的概念。事实上,浮点型在计算机中的存储方式和整型是两种不同的存储方式,等会我会讲浮点型的存储方式。下面的说明基于32位(bit,1byte = 8bit,32个比特相当于4个字节)操作系统。64位也是同样的道理。   

      整型在存储过程中首先要翻译成二进制的原码,然后再转换成反码,再加一即可得到补码,原码,反码,补码这三种表示方法都带有字符位与数值位二部分,最高位也就是32位是符号位,用“0”表示正,用“1”表示负数。

原码:直接将二进制按照正负数的形式翻译成二进制。

 反码:将原码的符号位不变,其他位(第1至31位)按位取反(每一位取反,是0就取反成1,是1就取反成0)。
 补码:将反码加1就是补码。

数据在内存中的操作,其实是对补码进行操作的。

     对于正数来说,他的原码,反码,补码都是相同的,可以直接输出。

      对于负数来说,他的原码,反码,补码遵守这样的规则:将这个负数翻译成二进制(原码),在符号位不变,其他位按位取反(反码)后加一(补码)存在内存中。在内存中进行操作之后的结果,还原成原码(补码-->反码-->原码)输出。

如:

int main()
{
	int a = 20;
	//4byte = 32bit
	//00000000000000000000000000010100
	//00000000000000000000000000010100
	//00000000000000000000000000010100

	int b = -10;
	//10000000000000000000000000001010	   - 原码
	//11111111111111111111111111110101     - 反码
	//11111111111111111111111111110110     - 补码
}

&b在内存中的情况: 

 

      在电脑中调试界面中打开内存这个选项,再输入&b即可看到在内存中表示的是16进制,(在64位操作系统下,打开微软自带的计算器,转到程序员,把-10的2进制的补码粘贴到BIN栏中,即可看到它的16进制)。

 

不过,大家可能会看到它转换成16进制与在内存中存储的16进制有些不一样,其实这就要介绍大小端的存储方式。

  2、大、小端的存储模式

举个例子:

     在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的 short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位 或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。比如0x11223344,它在内存中可能的存放形式有 44 33 22 11(0x) ,11 22 33 44  (0x) 或其他的,但其他的存放方式不容易记下顺序,而44 33 22 11(0x)和11 22 33 44(0x)就相对的方便。我们常用的 X86 结构是小端模式,而 KEIL C51 则 为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式 还是小端模式。

 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地 址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。

 简单记忆,就是在内存条中,内存地址由低到高。大端是高对高;小端是低对高。

 

3、整型提升 

实际上,加法操作不能用于字符型,有时我们会遇到这样的情况:

 两个运行的结果居然不一样,由先前的表可以看出,char的类型的范围是-128~127,因为char只占一个字节,所以就只有8bit位,在这个过程中还发生了截断与整型提升。 

%d算的就是c的原码,所以就打印出-124,而(a+b)的%d算的顺序就是a整型提升得出来的二进制数加上b的整型提升得出二进制数,再变为原码,进而就是132。

4、char类型的范围分析

为什么char类型是-127到128,而unsigned char是0~255呢,unsigned char是无符号字符型的意思,我们来画图分析一下:

由此可见signed char与 char的范围是-128~127,而unsigned char是0~255。(前面还有0000 0000,我这个0忘画了)。short,int类型都可以自己分析分析。

5、好题详解 

来看这一道题:

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0; 
}

这段代码结果输出为多少呢?

 

 我们先要知道%u 是打印无符号整形,认为内存中存放的补码对应的是一个无符号数,
%d 是打印有符号整形,认为内存中存放的补码对应的是一个有符号数。

-128

1000 0000 0000 0000 0000 0000 1000 0000   -128的原码
1111 1111 1111 1111 1111 1111 0111 1111           反码
1111 1111 1111 1111 1111 1111 1000 0000         补码

截断就为1000 0000

整型提升: 1111 1111 1111 1111 1111 1111 1000 0000

打印无符号整型 所以就是这个二进制数的十进制。

 

看这一道题:

unsigned int i;
for(i = 9; i >= 0; i--) {
    printf("%u\n",i);
}

 这道题其实打印的结果是死循环,因为unsigned是无符号,所以它的循环条件一定大于0,所以会一直打印。

 其实这些题的关键是计算机里整型里的计算一定是它的补码进行计算,还要注意每个类型的范围。

二、浮点型在内存中的存储

1、浮点型在内存中的表示

根据国际标准 IEEE (电气和电子工程协会) 754 ,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
符号说明:
(-1)^s 表示符号位,当 s=0 V 为正数;当 s=1 V 为负数。
M 表示有效数字,大于等于 1 ,小于 2
2^E 表示指数位。

比如浮点数字5.5:

写成二进制为:101.1

1.011 *2^2----二进制的科学计数法

由于上面的形式可以知道,他表示为:

-1^0*1.011*2^2

S=0;M=1.011;E=2; 

IEEE 754规定: 

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

 

 2、对于有效数字M与指数E的特殊规定

先说M:
前面说过,1<=M<2,M总是可以写成1.×××××× (因为十进制的数转成二进制了,第一位1总不是0),于是M在存储时就只存小数点后面的就可以了。比如,1.01在存储时就只存01就可以;1.100101存100101;(不可能出现0.10101这样的,这样的数小数点还可以向后移,也不可能出现0.5这样的,因为是二进制嘛)这样做就可以节省一个比特位,使有效数字的位数多出一位,等到读取的时候,再把第一位的1加上去就可以啦。
再说E:
1.首先,E为一个无符号整数(unsigned int),这就意味着,如果E占8位,它的取值就是0~255;如果E占11位,它的取值就是0~2047。但是我们知道,科学计数法中指数E是可以为负数的,所以IEEE就规定,存入内存时,E的真实值必须加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
举个例子:
2^10中E的值是10,所以保存浮点数时,E就存的不是10,而是10+127 = 137;换算成二进制就是 1000 1001(0111 1111
+ 0000 1010 = 1000 1001(127+10=137))
2.然后,E取出的时候还要再分为3种情况:
a.E不全是0 或 E不全为1
这时,浮点数就采用下面的规则表示,即E的计算值要减去127(或1023),得到它的真实值,再将有效数M前加上第一位的1
比如:0.5的二进制形式为0.1,由于第一位必须是1,就可以将小数点右移一位变成1.0,于是它的形式就是:(-1)^0 * 0 * 2^(-1),则这时的E就应该是(-1)+127 = 126,换成二进制是(0111 1110)而M去掉第一位的1是0,则float类型的 0.5在内存中是指这样存储的(32位):



b.E全为0
这时,浮点数的指数E等于1-127(或1-1023)即为真实值,有效数字M不再加上第一位,而是还原为0.××××××的小数,这样做是为了表示±0,和很接近0 的数。
c.E全为1
这时,如果有效数字M全为0,表示±无穷大,是正还是负取决于符号位S。

 3、好题详解

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; 
}

打印结果为: 

第一个打印:

第一个printf,以整数形式放进去,再以整数形式拿出来,就直接打印9。

第二个打印:

先以整型方式去存储,再以*p浮点形式拿出来,那就用浮点方式进行解读了。

8的补码:0000 0000 0000 0000 0000 0000 0000 1000

再变为浮点数的形式:0  |   00000000  | 00000000000000000001000,当E为全0的时候,有效数字M不再加上第一位,M就变成了0.00000000000000000001000,E就变为-126,它是正的,所以表示为(-1)^0 *0.00000000000000000001000*2^(-126),是一个非常小的数字,所以打印0.0000000000000000000......又因为是百分号f形式打印的,所以就打印出六位。

第三个打印:

由代码所知,它是以浮点数的形式来存储的,然后又以%d的形式进行打印出来,

9.0在浮点形式存储是:0|  1000 0010 | 00100000000000000000000

因为是用整型,所以当做补码 0100 0001 0001 0000 0000 0000 0000 0000,又因为正数补码等于原码,所以打印的数非常大,用计算机打印:

第四个打印:

以浮点数的形式放进去,以浮点数的形式拿出来,并且用%f的形式打印,小数点后面打印六位,所以是9.000000。

 

希望本次的知识能够对你有所帮助!!!