【C语言】数据的存储

整型在内存中的存储

我们知道,一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型来决定的。

	int a = 20;
	int b = -10;

因此上面的a、b变量会在内存中分配4字节的空间,但是他们在内存中实际上是如何存储的呢?这就需要引入原、反、补码三个概念。

原码、反码、补码的概念及其性质

整数的2进制表示有三种形式
原码:直接通过正负的形式写出的二进制序列就是源码
反码:原码的符号位不变,其他位按位取反得到的就是反码
补码:反码+1,也是真正存储在内存中的
其中,正整数的原码、反码、补码相同,而负整数的原码、反码、补码需通过计算得到

此外,在有符号数中,首位是符号位,并不作为数值大小的一部分。结合上面的知识点我们就可以将上面两个整数的原反补码写出:

	int a = 20;
	//原码:00000000000000000000000000010100 -> 0x00000014
	//反码:00000000000000000000000000010100 -> 0x00000014
	//补码:00000000000000000000000000010100 -> 0x00000014
	int b = -10;
	//原码:10000000000000000000000000001010 -> 0x8000000a
	//反码:11111111111111111111111111110101 -> 0xfffffff5
	//补码:11111111111111111111111111110110 -> 0xfffffff6

而在内存中我们总会看到数据的存储顺序与我们书写的顺序并不一致,这就需要引入大小端字节序存储的概念。引入

大小端字节序存储

大端字节序存储

把一个数据的高位字节序的内容存放在低地址处,把低位字节序的内容放在高地址处,就是大端字节序存储。如图所示:
大端字节序存储

小端字节序存储

把一个数据的高位字节序的内容存放在高地址处,把低位字节序的内容放在低地址处,就是大端字节序存储。如图所示:小端字节序存储
不难看出,vs2019版本的编译器为小端存储方式。
vs2019

编程判断当前编译器为大端or小端存储

这里我们借助整型数字1来进行判断:判断大端or小端
由图可知,在大端存储(图中上半部分)中,数字1的数值是存储在高地址处的;而在小端存储(图中下半部分)中,数字1的数值是存储在低地址处的。我们知道,内存中一个字节存储了两个十六进制数字,因此我们可以利用一个字符指针指向数字1,使这个指针去访问数字1的最低地址处的一个字节的数据,如果是1,则为小端,否则为大端。实现方法如下:

#include<stdio.h>

int check_sys(void)
{
	int a = 1;
	return *(char*)&a;
}
int main()
{
	if (check_sys()) 
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

练习题讲解部分

在了解了整型数在内存中存储方式的两个关键概念后,我们通过几道练习题来进行巩固:
习题一:
请写出代码的输出结果。

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	 
	printf("a=%d,b=%d,c=%d\n",a,b,c);

	return 0;
}

首先,char 和 signed char 或 unsigned char 是否为相同类型是取决于编译器的,与 int 和 signed int 的情况并不一致,这是因为这种情况在c标准中并未对其进行定义。在这里我们根据vs2019的定义方法,认为char与signed char为同一类型。
由于字符类型在内存中占据1个字节,也就是8个比特位,因此字符类型的值的大小可以通过下面的图表进行表示:在这里插入图片描述

在signed char中,第一位为符号位,所以只有后七位表示数值大小。因此该序列的表示范围为0、1、2、…126、127、-128、-127、…-3、-2、-1,共计256种,其中10000000为-128是c语言标准规定;
而在unsigned char中,八位均用来表示数值大小,因此该序列的表示范围为0~255,共计256种。

所以在上面的代码中,想要将-1(本身为int类型)存入signed char 或 unsigned char类型的变量中要进行截断。我们知道-1在内存中存储的数值(补码)为0xffffffff,截断保留最后一个字节为0xff,因此最后存入变量a、b、c的数值为11111111。代码中printf函数要求以%d也就是整数形式进行打印,因此要对字符型变量进行整型提升,由于a和b两个变量均为有符号字符型,又因为二者的符号位(最高位)均为1,因此在数字前面补1补至32位,最后结果为0xffffffff,因此二者的打印值为-1;但在unsigned char中c则作为一个无符号数进行补位,也就是在前面补0补至32位,最后结果为0x000000ff,因此打印结果为255。
在这里插入图片描述
习题二:
请写出代码的输出结果。

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

有了上一道题作为基础,这道题的思路就会清晰很多。首先我们知道-128作为int类型数字在存入char类型的变量时要进行截断(其实-128是在char类型的范围之内的,但是还是要走一遍流程,加深印象)-128的补码为0xffffff80,因此截断后变量a在内存中实际存储的数值为0x80,也就是二进制数10000000,又因为printf函数中要求要以无符号整型进行打印,因此仍要对变量a进行整型提升。这里要再强调一个点,整型提升时确定变量是否为有符号数取决于其原来的类型,而不是取决于将要打印出来的类型,因此0x80(16)作为一个有符号数(char)要在前面补1补至32位(因为首位为1,将其视为负数)。整型提升后,printf函数会将提升的结果作为一个无符号整数进行打印(因为无符号数的原码、反码和补码相同),所以打印结果是一个非常大的数字,即4294967168。过程与结果如图:
在这里插入图片描述
在这里插入图片描述
与此同时,这里我们如果将%u改为%d,整型提升之后的结果0xffffff80(补码)就会被视为一个负数(因为其首位为1),打印结果也会因此不同:在这里插入图片描述
习题三:
请写出代码的输出结果。

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

此题与上一道题并没有大的差别,只是128真的超过了字符型变量的存储范围,因此要对整数128,即0x00000080进行截断处理,最后变量a中存储的值为0x80,后面的处理过程就与上一道题完全一致了。运行截图如下:
在这里插入图片描述
习题四:
请写出代码的输出结果。

int main()
{
	int i = -20;
	unsigned int j = 10;
	
	printf("%d\n", i + j);

	return 0;
}

经过前两道题的洗礼,再看向这道题的时候同学们可能就迟疑了,这题…不会有坑吧?但实际上答案是:没有。就是我们看到的-20+10=-10。如果不放心,我们可以拿出二者的补码来算一算。
在这里插入图片描述
在这里插入图片描述
习题五:
请写出代码的输出结果。

int main()
{
	unsigned int i;

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

	return 0;
}

由于变量i的类型为无符号整型,所以i永远都不会小于0,因此循环为死循环,循环打印0~4294967295之间的数字。

习题六:
请写出代码的输出结果。

int main()
{
	char a[1000];
	int i;

	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d",strlen(a));

	return 0;
}

代码的输出结果为255。
因为a是一个字符数组,所以数组之中的每一个变量均为字符型变量。我们知道字符型在内存中存储的数字的范围(有符号)为-128~127,题中a[i]的值从-1开始:-1、-2、-3、…-127、-128、127、126、…2、1、0、-1…按照这个规律循环一千次,但是最后打印时我们调用了strlen函数,这个函数求长度的方法是寻找’\0’(或者说0也可以)来判断结束,因此在字符数组中遇到第一个’\0’就会停下来,因此从-1开始到1结束的一个循环共255个字符,因此打印255。
在这里插入图片描述
习题七:
请写出代码的输出结果。

#include<stdio.h>

unsigned char i = 0;

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

代码输出结果为死循环输出hello world\n。
同样的问题,unsigned char类型的变量的数值范围为0~255。而代码中的循环条件为只要i在0到255之间就会继续循环,当i=255完成循环进行自增运算时会直接变为0,条件符合并开始下一次循环,因此循环会一直进行下去。

结束语

本篇博客讲解了有关于数据在内存中存储的部分基本知识以及一些练习题的讲解,如有不足或遗漏之处还请大家指正,笔者感激不尽;同时也欢迎大家在评论区进行讨论,一起学习,共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值