【C语言】数据在内存中的存储

今天我来分享一下我近期学到的相关内容:

一.整数在内存中的存储

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

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

1.整数在内存中的存储

我们以前在学习二进制的时候,就知道整数在内存中以补码的形式存在,负数要原码取反加1得到补码,而正数或0的原码,反码(符号位不变,其它的取反),补码相同。

那为什么在内存中要以补码的形式存储呢?

最简洁的回答是简化算法,从而简化硬件

这么好的东西,我们要好好研究一下。

2.大小端字节序和字节序的判断

什么叫大端呢?就是数据的高字节内容存储在内存低地址处,而数据的低字节内容存储在内存高地址处。

小端就是数据的高字节内容存储在内存高地址处,低字节内容存储在内存低地址处。

内存中默认从左到右,从上到下,内存从低到高,这个说明这个编译器是小端字节序存储模式

我们画一个图来表示一下:

这样就清晰了。

下面我们来判断一下这个编译器是什么类型的存储方式吧:

看图:

确实是这样,用1去搞就可以了。

我们一起来练习一下看掌握情况吧:

第一题:

#include <stdio.h>

int main()
{
 char a= -1;
 signed char b=-1;
 unsigned char c=-1;
 printf("a=%d,b=%d,c=%d",a,b,c);
 return 0;
}
//我们先看第一个:-1是负整数,二进制(每个展开式中一个0或1就是一个bit位)表示为
//10000000 00000000 00000000 00000001(-1的原码)
//11111111 11111111 11111111 11111110(-1的反码)
//11111111 11111111 11111111 11111111(-1的补码)
//这个时候我们看char的脸色,char是signed还是unsigned,是取决于1编译器的,在vs系列下char类型是signed char,
//而在几乎所有的编译器下,int都是signed int。
//看char的脸色,char类型占一个字节8个比特位,删去前面的24个比特位得到111111111(还是属于“-1的补码”)。
//因为这个char类型的长度小于int的长度,要发生整型提升,补齐至32位,但这个时候还是要看char的脸色。
//char在这里是signed char,有正负号,这个时候"1"作为char的符号位,补齐,得到11111111 11111111 11111111 11111111(但还是属于-1的补码)
//这个时候看%d的脸色了,%d是打印有符号整型的,这个时候“-1”还是作为补码存在,要取反加一得到原码进行输出。
//10000000 00000000 00000000 00000000(“-1”的取反);
//10000000 00000000 00000000 00000001(“-1”的加一,得到“原码”,打印在我们面前,是-1)
//输出的应该是-1。

//我们现在看第二个:
//因为signed char在这里就是char,那么结果和第一个相同,都打印-1。

//我们看第三个:
//-1是负整数,二进制(每个展开式中一个0或1就是一个bit位)表示为
//10000000 00000000 00000000 00000001(-1的原码)
//11111111 11111111 11111111 11111110(-1的反码)
//11111111 11111111 11111111 11111111(-1的补码)
//现在来了一个unsigned char,我们就要发生截断,至11111111(还是-1的补码)
//因为char类型的长度小于int类型,要发生整型提升,这里要看unsigned char的脸色了,它只会认非负数,所以这个
//整型提升它会补上24个0,得到00000000 00000000 00000000 11111111(还是-1的补码,放在内存中)
//现在轮到%d上场了,%d(打印有符号整型)看到这个00000000 00000000 00000000 11111111(补码)就是正数啊,不需要取反加一,直接计算
//输出就好了,是255.

我们来看一下结果:

是正确的。

第二题:

#include <stdio.h>

int main()
{
 char a = -128;
 printf("%u\n",a);
 return 0;
}

说这个我们先来补充1一个知识点:求char类型的范围:

这样看到char+1是什么结果了吧,127+1=-128,-1+1=0,就像一个环一样:

现在我们来正式的解这道题:

//先看-128是整数,就要以补码的形式存储在内存中,这是负整数,10000000 00000000 00000000 10000000(-128的原码)
//11111111 11111111 11111110 01111111(-128的反码)
//11111111 11111111 11111111 10000000(-128的补码)
//现在来了一个char(一个字节8个bit),要发生截断,得到10000000(还是-128的补码)
//char类型的长度小于int类型的长度,要发生整型提升,这个时候要看char的脸色,char(有符号)支持直接补齐符号位
//得到11111111 11111111 11111111 10000000(但是还是-128的补码,只要是补码就存放在内存中)
//发生整型提升后,就要看%u的作为了:%u是打印无符号整型的,这里直接把符号位1当做一般二进制位,虽然现在它是补码,但%u认为
//是无符号,不用转换直接把它当做原码输出为11111111 11111111 11111111 10000000
//故答案是把它转换为十进制的结果,为4294967168

我们来看一下:

就是它。

第三题:

#include <stdio.h>

int main()
{
 char a = 128;
 printf("%u\n",a);
 return 0;
}
//128是整数,正整数,原码等于补码,存放在内存中为00000000 00000000 00000000 10000000(128的原反补)
//这个时候考虑到要用char类型的存储它,要发生截断得到10000000(还是128的补码)
//此时要发生整型提升,看char的脸色,char是有符号的,补上24位符号位,得到11111111 11111111 11111111 10000000(还是128的补码)
//现在看%u的脸色,%u是打印无符号十进制整型的,它直接把内存中的玩意儿看成一个完美的二进制序列,直接把它当成原码为
//11111111 11111111 11111111 10000000来输出,和上面的结果相同。

答案是

第四题:

#include <stdio.h>

int main()
{
 char a[1000];
 int i;
 for(i=0; i<1000; i++)
 {
 a[i] = -1-i;
 }
 printf("%d",strlen(a));
 return 0;
}
//a数组里面的元素全都是char类型,范围是-128~+127
//a[0]=-1;a[1]=-2;a[127]=-128;a[128]=127......a[255]=0。a[254]=1,为极限,应当打印255。
//最好画出环状图以方便理解

答案是

第五题:

#include <stdio.h>

unsigned char i = 0;

int main()
{
 for(i = 0;i<=255;i++)
 {
 printf("hello world\n");
 }
 return 0;
}

由于i是unsigned char类型,范围是0到255,+1从255跳到0,因此会造成打印死循环。

第六题:

#include <stdio.h>

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

无符号整型,根本不可能小于0,没错,又是死循环。

第七题:

#include <stdio.h>

int main()
{
 int a[4] = { 1, 2, 3, 4 };
 int *ptr1 = (int *)(&a + 1);
 int *ptr2 = (int *)((int)a + 1);
 printf("%x,%x", ptr1[-1], *ptr2);
 return 0;
}

我们先画一下内存布局图:

打印出来答案是一样的,我们要注意画图啊!

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

查看浮点数的定义在头文件<float.h>中查看。

浮点数家族还有float(精确到小数点后六位),double(精确到小数点后两位),long double(至少精确到小数点后两位)类型。

我们的浮点数在计算机中存储方式或表达方式和其他类型的是不一样的,知道了这个,我们才有可能做对相关的题目。

根据国际标准IEEE(电⽓和电⼦⼯程协会)754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

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

其中S的取值是0或1,控制符号位;

M是大于等于1且小于2的一个数;

E是一个整数,可正可负可为零。

我们画图来解释一下:

这里的S的值是0,M的值是1.011,E的值为2,就像十进制里面101.1=1.011*10^2一样,这里是二进制,就是(-1)^0*1.011*2^2,类比的思想。

IEEE 754规定:

32位(32bit,4Byte)的浮点数,第一个bit用来存放S,往后的8个bit用来存放指数E,再往后的23个bit位用来存放有效数字M。(float)

64位(64bit,8Byte)的浮点数,第一个bit位用来存放S,往后的11个bit位用来存放指数E,再往后的52个bit位用来存放有效数字M。(double,long double)

我们画一个图来表示一下:

这样我们就可以理解它是在内存中怎么存放的,不过这个也是二进制数位呢!

注意:因为M是前面有一个1,在实际储存的时候会不要这个1,就可以多精确一位(23变24,52变53)。

注意:因为E可能为负数,那么我们要进行转换一下,在32位(float类型)下,E加上中间数127得到的二进制数从左到右放到那8个bit位上;而在64位(long double,double类型)下,E加上中间数1023得到的二进制数放到那11个bit位上(小心:E在转换后的储存范围对应的bit未包含符号位)。

上面的情况是在E加上中间数得到的二进制序列都不为0或都不为1的情况下。

特例1:如果“E”二进制位全为0,E为-126(1-127得到的,32位下),则代表这个数无限趋近于0(看符号位怎么样取正负),同时M原来的开头1变成0,变得更小了(注意:M如果不能补齐那对应的位,其余的要补充为0)。

特例2:如果“E”二进制位全为1,且有效数字M的位全为0,代表这个数趋近于正负无穷(看符号位啊)。

现在我们通过一道题外加画图,内存调试观察的方式来解答一道题目:

#include <stdio.h>

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;
}
#include <stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);//9
	printf("*pFloat的值为:%f\n", *pFloat);//n的原反补都是00000000 00000000 00000000 00001001在内存中
    //我们看一下第二个:由于int和float都是4个字节,且两者存储方式不一样,因此我们要先画出float的内存布局:
    //V=9.0;十进制
    // V=1001.0;二进制
    // V=1.001*2^2=(-1)^0*1.001*2^2;S=0,M=1.001,E=3;
	//这里要按照%f的形式来打印,重新划分为0 00000000 00000000000000000001001
	//这里E位全为0,说明E=-126非常小(指数非常小,M有效位也小的可怜,因此打印出来的结果是0.000000)
	*pFloat = 9.0;//32位下画出来:0 10000001 00100000000000000000000(float 9的情况)(重新去赋值啊)
	printf("num的值为:%d\n", n);//按%d的形式去打印,%d一看内存中是这玩意,正数,直接当做原码打印,好大好大,是1083179008
	printf("*pFloat的值为:%f\n", *pFloat);//本来就是9.0,按照一样的,打印出9.0
	return 0;
}

我们来看一下答案:

一定要注意计算,不要看错了。

好了,今天我们的就讲完了,如果有补充等,欢迎各位大佬指点!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值