c语言学习17——数据在内存中的存储

一、整数在内存中的存储

  • 整数在内存中是以二进制的方式进行存储的。
  • 整数的二进制表示方式有三种:原码、反码和补码。在计算机中存储的是整数的补码。其中有符号整数由符号位+数值位组成,最高位一位为符号位,剩余位是数值位。正数的符号位为0,负数的符号位为1。
  • 正数的原反补都相同,负数:原码—>(取反)反码—>(+1)补码。

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

整数在内存中的存储,存储的内容是整数的补码,存储方式有两种,一种是大端字节序存储,另一种是小端字节序存储。

2.1 什么是大小端

大端字节序

高位字节内容存储到高地址,地位字节内容存储到低地址。

小端字节序

高位字节内容存储到低地址,低位字节内容存储到高地址。

例如:
在这里插入图片描述
vs2022中使用的是msvc的编译器,支持的是小端字节序存储方式,因此我们想将一个十六进制的数字存储到计算机内存中时,计算机会将0x112233按照“高位字节内容存储到低地址,低位字节内容存储到高地址。”的存储规则进行存储。结果如下:
在这里插入图片描述
通过内存监视窗口,得到a的地址存放的内容,我们可以看到112233在内存中是反向存储,符合小端字节序存储。

2.2 判断大小端

写一个程序,判断编译器是大端存储还是小端存储。

char judge()
{
	int i = 1;
	return *((char*)&i);
}

int main()
{
	char a = judge();
	if (a == 1)
		printf("小端");
	else
		printf("大端");

	return 0;
}

在这里插入图片描述
注意,1在16进制内存下表示为0x00000001,如果是小端存储:01 00 00 00;如果是大端存储:00 00 00 01.因此我们只需要将第一个字节通过强制类型转换取出即可,如果是1则为小端,反之为大端。同理,可以将i的值换为其他符合条件的值。

2.3 为什么有大小端

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit 位,但是在C语⾔中除了8 bit 的 char 之外,还有16 bit 的 short 型,32 bit 的 long 型(要看具体的编译器),另外,对于位数大于8位的处理,例如16位或者32位的处理器,由于寄存器宽度大于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

2.4 练习

  1. 打印结果是什么
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;
}

答案:

int main()
{
	char a = -1;
	//-1:原码:10000000000000000000000000000001
	//   补码:11111111111111111111111111111111
	//a时char类型,只能存放8个字节,即11111111
	signed char b = -1;
	//同理,b中存放的也是11111111
	unsigned char c = -1;
	//同理,c中存放的也是11111111
	printf("a=%d,b=%d,c=%d", a, b, c);
	//因为打印占位符用的%d,即要求打印十进制的有符号整型,因此需要对char类型进行整型提升
	//a是有符号char,最高位为符号位,因此补符号位:11111111111111111111111111111111,这是补码且为负数,因此取反加一得到的结果是-1
	//b同理也是有符号char,打印结果为-1
	//c是无符号char,没有符号位,因此整型提升时补0:00000000000000000000000011111111,当用%d打印时,将第一位看成符号位,因此是正数,补码=原码,即打印255
	return 0;
}
  1. 打印的结果是什么
int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}

答案:

int main()
{
	char a = -128;
	//-128的原码是:10000000000000000000000010000000
	//      补码是:11111111111111111111111110000000
	//a是char类型,只能存8个bit位,即10000000
	printf("%u\n", a);
	//%u打印十进制无符号整数,需要对a进行整形提升
	//因为a是有符号char,所以提升时补充符号位:11111111111111111111111110000000
	//因为打印是无符号整型,因此直接打印整型提升后的数据,结果为4,294,967,168
	return 0;
}

变式:

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

答案:

int main()
{
	char a = 128;
	//128的原码:00000000000000000000000010000000,补码也是这个
	//a中存8个bit位,10000000
	printf("%u\n", a);
	//%u打印十进制无符号整数,需要对a进行整形提升,因为char是有符号char,所以补符号位:11111111111111111111111110000000
	//打印结果为:4294967168(不变)
	return 0;
}
  1. 打印结果是什么
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

答案:
上述代码实现向数组a中每个元素进行赋值。从下标为0的元素开始赋值-1,然后下标每+1,值每-1。看下图:
在这里插入图片描述
可以将char的取值范围想象成一个圆环,a数组从-1开始取值,沿圆环逆时针赋值,当赋值到0是一共255个元素,即整个char的取值范围(-128–127),所以strlen求出的a数组的长度为255。

  1. 打印结果是什么
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}

答案:
无符号char的取值范围是0-255,所以i永远<=255,所以会一直打印hello world,陷入死循环。

变式:

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

答案:
无符号整型i的取值永远>0,所以还是恒成立的条件,陷入死循环。

  1. 打印结果是什么
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;
}

答案:

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//ptr1指向数组末尾
	int* ptr2 = (int*)((int)a + 1);
	//ptr2指向数组第二个字节位置,整数+1就是单纯+1
	printf("%x,%x", ptr1[-1], *ptr2);
	//ptr1访问的数组倒数第一个元素,即4,%x打印4
	//ptr2从第二个字节开始,向后访问4个字节,%x打印得到2000000
	return 0;
}

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

常见的浮点数有小数(3.14)、科学计数法(1E10)等,浮点数类型包括:float,double,long double类型。浮点数类型所在的头文件:float.h

3.1 示例

下列代码会输出什么?

int main()
{
	int n = 9;
	float *pFloat = (float *)&n;
	printf("n的值为:%d\n",n);
	printf("*pFloat的值为:%f\n",*pFloat);
	
	*pFloat = 9.0;
	printf("n的值为:%d\n",n);
	printf("*pFloat的值为:%f\n",*pFloat);
	
	return 0;
}

答案:
在这里插入图片描述
这是为什么呢?——这是因为浮点数和整数在内存中的存储方式不同。

3.2 浮点数在内存中的存储

浮点数在内存中的表示方法:

在这里插入图片描述

  • S表示该数字是否为正数,S=0表示正数,S=1表示负数。
  • M表示实际存储的二级制位,1<= M <2。
  • E表示该数据的指数位。

举例:
十进制的5.0表示为二进制:101.0,写成浮点数存储的方式:(-1)0 * 1.01 * 22

对于32位的浮点数来说,最高位表示符号位S,紧接着8位表示指数E,剩余23位表示实际存储的M。

对于64位的浮点数来说,最高位表示符号为E,紧接着11位表示指数E,剩余52位表示实际存储的M。

此外还有一些特别的规定:

  • 对于M来说,M的值是大于等于1小于2的,因此小数点之前肯定是1,那么我们在存储时可以将小数点之前的1省略掉,然后多存储一位小数位,则我们可以存储小数点后24位。
  • 对于E来说,既可能为正也可能为负,所以规定E的取值要加上一个中间数再进行存储,在32位下中间数是127,在64位下中间数是1023。

指数E在提取中会出现三种情况:

  • E为全0:E为全0意味着在32(64)位下,E+127(1023)后大小为0,则规定E的真实值为1-127(1-1023)。
  • E为全1:E为全1意味着在32(64)位下,表示±无穷大,符号位取决于S。
  • E不为全0也不为全1:将E-127(1023)再正常计算即可。

3.3 回顾练习

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//ptr1指向数组末尾
	int* ptr2 = (int*)((int)a + 1);
	//ptr2指向数组第二个字节位置,整数+1就是单纯+1
	printf("%x,%x", ptr1[-1], *ptr2);
	//ptr1访问的数组倒数第一个元素,即4,%x打印4
	//ptr2从第二个字节开始,向后访问4个字节,%x打印得到2000000
	return 0;
}

解析:

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	//9在32位下二进制表示:00000000000000000000000000001001
	printf("n的值为:%d\n", n);
	//n为整形,以%d打印直接打印有符号整形,得到9
	printf("*pFloat的值为:%f\n", *pFloat);
	//pFloat是一个浮点型指针,存储时按照浮点数进行存储
	//0 00000000 00000000000000000001001
	//S    E              M
	//(-1)^0 * 00000000000000000001001 * 2^-126
	//由于浮点数默认打印小数点后6位,因此得到值为0.000000
	
	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	//9.0先写成二进制:1001.0
	//写成存储标准形式为:(-1)^0 * 1.001 * 2^130
	//所以内存中存储为01000001000100000000000000000000
	//最高位0表示正数,补码=原码,直接读取得到1091567616
	printf("*pFloat的值为:%f\n", *pFloat);
	//浮点数的形式直接打印9.0,默认打印六位小数,9.000000

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值