数据在内存中的存储——整数

何以称英雄人物?识以领其先。——袁枚


1、整数的存储

任意一个整数(当然是不能超过INT_MAX的一个数字),都是以2进制的表示方式存储的,表示方法有三种,分别为原码,反码,补码
而这三种方法都是既有符号位又有数值位的两个部分,符号位都是0来表示“正”,用1来表示“负”,最高的那位被当作是符号位,剩下来的31个bit全是数值位。
正数的三种表示形式都是相同的
而负数三种表示方式不同

原码:直接将数值按照正负数的形式,表示为二进制,就是原码
反码:将原码的符号位不改变,其余的按位取反。
补码:反码+1得到。

当然不管是正数还是负数,整数的存储存放的就是补码。
关于为什么要存放补码存贮,其实真正的原因是因为,使用补码,可以将符号位和数值域统一处理,同时加法和减法也可以统一处理,并且原码和补码的相互转换的处理过程是相同的,不需要额外的硬件电路(符号位不变,取反,+1)

2、大小端字节序,字节序判断

在知道存储的方法后,那我们不经想起到底是怎么,才把这样的4个字节的数字在内存中存储的呢?难道就是我们写的1234,这样子转化为2进制后从前往后排列吗?其实,可以调试一下,既然在监视内存的时候会转化为16进制,那我们就设置一个值,整数,但是以16进制写,并且,还要能清楚方便哪是开头,哪是结尾,肯定是不能写一个全是一个数字的数吧。下面看这段!在这里插入图片描述
可以看到这里我们查看&a的时候他却是以44,33,22,11的形式存在,并且当我们把每一排的列数都是设置为1 的话,能够看见地址是从上向下的,也就是说,在数字低位置的情况下(个位是最低的),存在了低地址的地方。在这里插入图片描述
在这篇文章中,提到在VS2019环境下,使用出现了死循环,并且还介绍了使用的习惯和内存使用顺序可以看一下,了解了解,但是还是不相同的,千万别搞错了,这里是以一个一个数为存储来说。
当然要记住,下面的这些重要的东西。
在这里插入图片描述
==确实是在使用栈的时候是从高地址到低地址,但是打印出来的数值不一定从小到大,反而真正的情况还可能是向上面的图一样,高地址的打印数值小于低地址的。==也相当于,对于栈来说,是从上到下增长,从高地址到低地址,但是可能对于整个而言,有可能是从数值低的到高的。
在这里插入图片描述
这两幅图片是很重要的,要多加理解。

2、1大小端是什么?

其实,就像刚刚提到的一样,无论是什么,只要是超过一个字节的内存存储,一定是会有顺序问题的存在。
那么首先先介绍一下大小端是什么。
==大端:==是指数据的低位字节内容保存在内存中的高地址处,而数据高字节内容,保存在内存中的低地址处。
==小端:==是指数据的低位字节内容保存在内存中的低地址处,而数据高字节内容,保存在内存中的高地址处。
记住大小端的区别,方便区分!不同的编译器上面可能有不同的,在我使用的Visual Stdio上面是小端。

2、2大小端存在原因

为什么会有⼤⼩端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都
对应着⼀个字节,⼀个字节为8 bit 位,但是在C语⾔中除了8 bit 的 char 之外,还有16 bit 的
short 型,32 bit 的 long 型(要看具体的编译器),另外,对于位数⼤于8位的处理器,例如16位
或者32位的处理器,由于寄存器宽度⼤于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问
题。因此就导致了⼤端存储模式和⼩端存储模式。
例如:⼀个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么
0x11 为⾼字节, 0x22 为低字节。对于⼤端模式,就将 0x11 放在低地址中,即 0x0010 中,
0x22 放在⾼地址中,即 0x0011 中。⼩端模式,刚好相反。我们常⽤的 X86 结构是⼩端模式,⽽
KEIL C51 则为⼤端模式。很多的ARM,DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是
⼤端模式还是⼩端模式。

2、3模拟实现函数判断大小端

其实判断函数大小端也是比较容易的,因为当我们int a=1的时候,这时,a在内存中的存储,只有一个字节是有意义的数值,其他都是0,所以我们可以根据这点来写程序。

int check_sys()
{
	int i = 1;
	return (*(char*)&i);
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("xiao端\n");
	}
	else
	{
		printf("da端\n");
	}
	return 0;
}

这样的,可以确定机器本身到底是怎么来存储的。
==注意:==这地方的这段代码一定要了解清楚,不能马虎。

	return (*(char*)&i)
	return(char*)a

这两段代码是不一样的,并且能表示出大小端的只有第一个方法,第二个是不可以的。第二个的意思其实就是,直接把a转化为char,但是直接转化的话,无论是什么,他只会变为char类型的1,无法判断,到底是大端还是小端。
当然除了上面的方法之外,还有运用联合体的方法来辨别到底是大端还是小端。使用这段代码的情况下,不仅能很好的理解联合体,还能更深刻的理解什么是大小端并且知道联合体是怎么储存的。

int check_sys()
{
	union
    {
		int i;
		char c;
	}un;
	un.i = 1;
	return un.c;
}

3、关于数据存储的问题

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

在这里插入图片描述
对于a来说,原本存贮的数字应该是10000000 00000000 00000000 00000001
然后因为是负数所以要取反11111111 11111111 11111111 11111110
最后一步,是加上1,的到最后的补码11111111 11111111 11111111 11111111
但是由于a的存储是char类型的,所以要发生截断,最后存储的时候却是11111111
相对于b来说步骤是一模一样的。
对于c来说步骤也是一样的。
类型在存储的时候没有什么用,只是存储的时候要多大的空间,只有当用的时候,才会有所不同。
所以这里要关注,要关注一下整型提升的问题
%d - 是以十进制的形式打印有符号的整数
整型提升:
1、当是无符号的时候,提升的时候,高位补0。
2、当是有符号的时候,提升的时候,是按照最高位置的补,0的话就补上0,1的话就补上1。
所以,对于a来说,a在整型提升的时候,会变为32位都是-1的数,那么也就是-1。对于b来说也是相同的道理,因为signed char就是等于char。**但是对于c来说,是无符号的,提升的时候要按照前位补0的方法,所以到最后c的值变为了255。**当然要记住这时候还是补码,需要取反加1才会变为原码。

3、2不同类型的取值范围造成的差别

就以char类型的取值范围来举例。以此类推,可以将各个的类型都能推断出来。
char类型是1个字节,8个比特位
**
00000000=1
00000001=2
00000010=3
·····
10000000=-128(此时取反+1,但是如果这样子的话会发现,会溢出,所以就是规定了此时就是-128)
10000001=-127
10000010=-126
·····
111111110=-2
111111111=-1(要取反后+1)
**
在这里插入图片描述
所以,对于上面写的一段来说,其实可以画图来简单的表示一下。

4、练习

4、1练习1(关于整型提升)

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

注意:%u是按照无符号的形式打印。
在这里插入图片描述
关于此时的a的原码是10000000 00000000 00000000 10000000
反码是11111111 11111111 11111111 01111111
补码是11111111 11111111 11111111 1000000
然后还要发生截断,所以最后a存储的只有10000000(12)
但是打印的时候是%u,发生整型提升,又是因为a是char所以按照有符号位的整型提升,高位全部补上1,但是最后又是%u打印无符号整型,所以就相当于打印一个数子的二进制为11111111 11111111 11111111 10000000在这里插入图片描述
这下结果就是对的了。
所以,整型提升的时候,是看a到底是有没有符号的,无论%d还是%u的打印方式,整型提升看的是数字的类型本身,只有打印的时候,才会在意打印的形式。

4、2练习2(关于整型提升)

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

在这里插入图片描述
为什么char a=128的时候还是原来a=-128的数字呢?
128时,a会发生截断,因为char类型放不下在,只能放下00000000 00000000 00000000 10000000,截断之后存放的时10000000却和上面的练习1中的char a=-128相同,存储的相同,所以最后结果也应该相同。

4、3练习3(关于0是\0)

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

在这里插入图片描述
啊啊啊啊?为啥这里又是255呢?怎么看也不应该是255吧,因为a是可以存1000个呢,怎么可能就255个啊。但是我们要注意到strlen的用法是什么,strlen在使用的时候,会注意到,当遇到\0的时候就会停止(计算\0之前的数字有多少)。但是数组里面也没有\0啊,但是\0的ASCII码确是0
从开始应该是怎么存储?-1,-2,-3,-4···-128,127,126,125···,2,1,0
在这里插入图片描述
正好0就是存放’\0’的,也可以通过调试看到有255个数是在\0之前的。
所以答案才会使255个。

4、4练习4(关于死循环)

int main()
{
	int i = 0;
	unsigned char a = 0;
	for (i = 0; i <= 255; i++)
	{
		printf("haha\n");
	}
	return 0;
}

此时就是死循环,因为unsigned char类型是不会比255数字还大存在的,所以判断条件是不会停止的,所以就会一直循环,不停止的打印。


还有剩下来的浮点数在内存中的存储,下章解释清楚

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值