栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言:数据在内存中的储存

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言:数据在内存中的储存

本次数据在内存中的储存会分为两部分:一是整型在内存中的储存;二是浮点型在内存中的储存。

一、整型在内存中的储存。首先,我们要知道原码、反码、补码,这三个在前面有做过详细的整理,这里就不过多讲述。接下来我们就来了解大小端(大端字节序&小端字节序)。

那么,什么是大小端呢?

大端字节序的存储方式:低位存在高地址,高位存在低地址。

小端字节序的存储方式:低位存在低地址,高位存在高地址。

(其中,大小端的最小单位为字节!)

接下来,会讲述如何用一个程序来判断所用的机器是大端还是小端。

#include 

int check_sys()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

int main()
{
	int ret = check_sys();   //返回1是小端,返回0是大端
	if (ret == 1)
	{
		printf("小端n");
	}
	else
	{
		printf("大端n");
	}
	return 0;
}

这个代码的主要思路是:check_sys函数中通过强制类型转换,提取出一个字节大小中所存的数据是否和内存中首个字节数据相等,若相等,则为小端字节序;若不等,则为大端字节序。

因为我们所取的值1是一个很特殊的值,所以,我们还可以对代码进一步做出简化,具体代码见下:

#include 

int check_sys()
{
	int a = 1;
	return *(char*)&a;
}

int main()
{
	int ret = check_sys();   //返回1是小端,返回0是大端
	if (ret == 1)
	{
		printf("小端n");
	}
	else
	{
		printf("大端n");
	}
	return 0;
}

在了解完原码、反码、补码和大小端的内容后,我们就可以来对有符号和无符号的整型进行研究,具体会通过以下几个题目进行解释:

题目一:

//以下代码会输出什么?
#include 
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;
}

对于a变量来说,这里-1是int类型,转成char类型会发生截断,因为内存中存储的是二进制的补码,所以,发生截断也是对内存中二进制的补码进行截断的,只会保留最低的8个比特位数据,又因为其最后是以%d的形式打印出来的,所以保留的这些数据还会发生整型提升,对于有符号的整型提升,会在二进制的最高位补符号位,最后再通过补码转换为原码,就会发现,最后打印出来a的值为-1。

对于b变量来说,虽然C语言中并没有规定char与signed char是相同的,但是在很多主流的编译器中,都会默认char就是signed char,就比如我经常使用的VS2019等,所以,最后b打印出来的结果也会和a一样,为1。

对于c变量来说,前面与有符号char一样,但是到了整型提升部分,对于无符号的整型提升,会在二进制的最高位补0,最后打印出来c的值就为255。

题目二:

//以下代码会输出什么?
#include 
int main()
{
	char a = -128;
	printf("%un", a);
	return 0;
}

首先,-128以补码存入内存中时,在char类型中会发生截断,因为char存的是有符号类型,所以在打印时会发生整型提升,会在二进制最高位补符号位,然后又因为是用%u打印的,所以在内存中会自动识别为无符号数,所以原码就会等于其补码,最后打印出来的数会非常大。

题目三:

//以下代码会输出什么?
#include 
int main()
{
	char a = 128;
	printf("%un", a);
	return 0;
}

这道题和题目二类似,只是把-128改为128,然后会发现,128在截断后放入的二进制序列与-128截断后的二进制序列相等,所以经过同样的计算过后,会打印出和题目二一样的一个非常大的数据。

题目四:

//以下代码会输出什么?
#include 
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%dn", i + j);
	return 0;
}

这是一道比较简单的题,用来区别前面几道题,直接用两数储存在内存中的二进制数据进行计算,得出的二进制再求原码打印出来,就会看到结果是-10。

题目五:

//以下代码会输出什么?
#include 
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%un", i);
	}
	return 0;
}

因为i是无符号整型,且打印的时候是以%u的形式打印,所以当i减为0的时候,在下一次循环的开始时,i为-1,其二进制的补码32个比特位会变成全1,在无符号的情况下,原码等于补码,这时候的i会变成一个非常大的值,然后这个非常大的值会继续减到为0,再变为非常大,这样无限循环下去。

题目六:

//以下代码会输出什么?
#include 
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

这是一道比较重要的题,因为char最多为8个比特位,当char为无符号时,其取值范围是从0到255;而在有符号char中,取值范围是从-128到127,但是,无论是有符号还是无符号,他们的长度(strlen)都是一样的,为255。所以,最后打印出来的结果为255。

推论:不同的整型类型都有一定的取值范围,且对于有无符号也有不同的取值范围。具体可以通过头文件limits.h转到文档进行查看。

题目七:

//以下代码会输出什么?
#include 

unsigned char i = 0;

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

这道题和上一道题类似,因为无符号的char的取值范围是从0到255,所以i的值是不可能大于255的,for循环中的条件是永远都成立,所以该程序打印时会无限循环。

二、浮点型在内存中的储存。这一部分会通过一个例子引出。

例子:

#include 
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%dn", n);   //9
	printf("*pFloat的值为:%fn", *pFloat);   //0.000000
	*pFloat = 9.0;
	printf("n的值为:%dn", n);   //1091567616
	printf("*pFloat的值为:%fn", *pFloat);   //9.000000
	return 0;
}

在看这段代码之前,我们需要知道浮点数和整数在内存中的存储规则是不一样的,不能用整数的那一套方法直接套用,那么,浮点数的存储规则是什么呢?

对任意二进制浮点数V都可以表示成下面的形式:(-1)^S*M*2^E

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

2.M表示有效数字,大于等于1

3.2^E表示指数位

例如:5.5 转换为二进制为 101.1(也即是-1^0*1.011*2^2)

通过上面描述,可以推断出,二进制浮点数在内存中只需要存入S、M、E三个值就好,那么这三个数在内存中是如何存储的呢?

对于单精度浮点数float(也就是32位浮点数)储存来说,最高一位是符号位S,接着8位是指数E,剩下的23位为有效数字M。

对于双精度浮点数double(也就是64位浮点数)储存来说,最高一位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

M的存入:因为有效数字M值的范围一直都是大于等于1而小于2的,所以储存的时候会将正数部分的1舍去,只将小数部分存到内存中。

E的存入:E为一个无符号数,若是负数,存入内存时E的真实值必须加上一个中间数,对于8位的E,这个中间数为127;对于11位的E,这个中间数为1023。

知道了浮点数是如何存入的,接下来,就要了解其是如何取出的。

从内存中取出的三种情况:

1.E不全为0或不全为1:指数E的计算值减去127(或1023),得到真实值;再将有效数字M前加上第一位1即可。

2.E全为0:指数E等于1-127(或1-1023)即为真实值;有效数字M直接就为0.XXX,这样就可以很容易的看出是一个无限接近于0的数。

3.E全为1:与E全为0相反,是一个接近于无穷的数。

通过上述这一连串的规则,我们就可以很好地做出上面那个例子,上述例子有四个打印,接下来,就把四个打印逐一列举出来。

第一个打印:因为是整型存入、整型取出,所以结果一定是打印出9。

第二个打印:因为是浮点数类型,所以会将原来内存中32个比特位进行还原,其中会发现E为全0。根据上面内存中取出的三种情况中的第二种不难看出,还原出来的浮点数是一个无限接近于0的数。

第三个打印:根据浮点数的转换规则,9.0转换为二进制为1001.0(即为-1^0*1.001*2^3),然后因为是要以整型的形式取出,所以通过浮点数写出这32个比特位,转为原码,以整型的形式读出来,就可以很好地得到1091567616这个结果了。

第四个打印:因为是浮点数存入、浮点数取出,所以结果一定是打印出9.000000。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/702794.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号