整数在内存中的储存(大小端字节序)
1.整数在内存中的储存
2.大小端字节序
3.整数在内存中储存例子
4.字节序判断
5.死循环现象
文章目录
- 整数在内存中的储存(大小端字节序)
- 整数在内存中的储存
- 大小端字节序
- 什么是大小端
- 为什么会有大小端
- 整数在内存中储存例子
- 字节序判断
- 死循环现象
整数在内存中的储存
在学操作符的时候,我们就知道整数的2进制表示方法有3种,原码,反码,补码
对于有符号整型,这三种表示方法都由符号位和数值位组成,符号位,’0‘表示’正‘,’1‘表示’负‘。
最高位为符号位,其余的为数值位。
对于有符号整型
正整数原码,反码,补码相同
负整数:
原码:直接将数值按照正负形式翻译成二进制
反码:原码符号位不变,数值位取反
补码:反码加1
对于无符号数据
原码反码补码相同
对于整形数据来说:内存中存放的是数据的补码
因为补码可以对符号位和数值域统一处理,同样,加法减法也可以统一处理,补码与原码互相转换,他的运算过程是相同的,不需要额外的硬件电路
大小端字节序
在学习大小端字节序之前我们先来梳理几个小知识
1.一个字节——>8个二进制位
一个16进制位——>4个二进制位
两个16进制位——>8个二进制位
2.在计算机系统中,内存被分为一个个字节单元,字节单元的编号==地址
下面给一个代码,试着调试看一看
int main()
{
int a = 0x11223344;//这里给出一个整型a,用16进制给初始化一下
return 0;
}
调试看结果
我们不难看出a中的0X11223344这个数字是按照字节为单位,倒着存放的,这是为什么呢?
这就不得不提到大小端了。
什么是大小端
当我们在内存储存超过一个字节的数据时,就存在储存顺序的问题,我们分为大端字节序储存和小端字节序储存
大端储存:
是指数据的低字节内容保存在高地址处,而数据得高字节内容保存在低地址处。
小端储存:
是指数据得低字节内容保存在低地址处,而数据的高地址内容保存在高地址处。
上边使用的VS2022,他是小端储存模式,所以按照小端的储存模式规则,他在内存中字节序是倒着储存的。
为什么会有大小端
存在大小端模式之分的原因是,在计算机系统中,我们是以字节为单位的,每个地址单元都对应一个字节,一个字节为8个bit位,但在C语言中,除了8个bit位的char类型,还有16个bit的short型,32个bit的long型(看具体编译器了),此外,对于位数大于8的处理器,比如16位或32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,这就导致了大端储存模式和小端储存模式。
比如,对于一个16个bit位的X,他在内存中的地址为0x0010,X的值是0x1122,那么0X11是高字节位,0X22是低字节位,在大端模式中,0x11应该放在低地址处,即0X0010,0X22应该放在高地址处,即0X0011中。小端模式,则恰好相反。
我们常用的X86结构是小端模式,而KEIL C51为大端模式。很多的ARM,DSP都是小端模式。有些ARM处理器还可以由硬件来选择大小端
整数在内存中储存例子
1.写一个程序来判断当前机器的字节序
分析一波
我们可以先创建一个整型变量,int a=1
这个数据在小端模式下储存的应该是
01 00 00 00
大端模式下储存的应该是
00 00 00 01,
因此想要判断当前机器是那种字节序,只需要拿出第一个字节比一比就行
如何把它取出来呢?
我们知道当我们对一个数据取地址时,得到的地址是较小的字节单元的地址,(这一点可以看一下博主的指针知识点总结,有讲到这里)而内存的储存是从低地址向高地址储存的,因此我们可以对a取地址就可以锁定到第一个字节,再将它强制类型转换成字符指针类型,然后解引用就可以得到第一个字节了
即
*(char *)&a;
由此,写出代码
int Check_c(int i)
{
return *(char*)&i;
}
int main()
{
int i = 5;
int ret=Check_c(i);
if (ret == 5)
printf("小端\n");//如果当前的系统储存方式是小端,就能输出5,如果不是小端返回值就不会是5
else
printf("大端\n");
return 0;
}
下面再通过几道题,再理解一下
2.
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;
}
再分析之前,先来复习一下整型提升的知识
对于有符号整数提升是按照变量的数据类型的符号位来提升
对于无符号整数提升,高位补0
分析一波
我们先写出-1的原码反码补码
-1
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 111111111 11111111 1111111
又因为a,b,c均是char类型,这里截断一下,a,b,c都是
11111111
在输出的时候要以有符号整形输出,那么就要发生整型提升
对于a,他是有符号的char类型,高位补1,得到完整的补码,
11111111 11111111 11111111 11111111
对其补码取反加1,得到的原码为
10000000 00000000 00000000 00000001
输出仍为-1,而对于b,他也是有符号char类型,与a一样,输出-1
对于c,他是无符号类型,做完整型提升得到补码
00000000 00000000 00000000 11111111
同时这也是c的原码,输出255
看一下运行结果
3.
int main()
{
char a = 128;
char b = -128;
printf("a=%u b=%u", a, b);
return 0;
}
在分析之前,还是先来复习一下知识
char类型通常是一个8位的有符号整数,取值范围在-128~127
unsigned char 类型是8位的无符号整数,取值范围在0~255
这里我们分析a,b,他们的原反补码如下
-128
原码:10000000 00000000 00000000 10000000
反码:11111111 11111111 11111111 01111111
补码:11111111 11111111 11111111 10000000
a,b是char 类型,截断得到a,b是
10000000
对a,b整型提升后补码
11111111 11111111 11111111 10000000
因为输出的是无符号整型,a,b的原码反码补码一样,输出的是一个很大的数
看运行结果
在这里尽管a=128,已经超出char类型的取值范围,但并不影响,因为在char类型里边他总会把赋给它的值通过各种截断,让他在他的取值范围内
而我们看到在截断后,a,b的值是一样的,所以他整型提升后,结果也是一样的
4.
int main()
{
unsigned char a = 200;
unsigned char b = 100;
unsigned char c = 0;
c = a + b;
printf(" % d % d", a + b, c);
return 0;
}
先分别求出a,b的反码
a的反码:00000000 00000000 00000000 11001000
b的反码:00000000 00000000 00000000 01100100
那么在unsigned char的类型中
a:11001000
b:01100100
a+b:1 00101100
对他做整型提升高位补0
00000000 00000000 00000001 00101100(原码,反码,补码)——>对应的就是十进制的300
而c=a+b;,
虽然unsigned char 下的a+b仍为1 00101100,但c是char类型,要丢掉高位的1,c为 00101100,对其进行整型提升,高位补0,
00000000 00000000 00000000 00101100,对应十进制的44
字节序判断
1.
unsigned int a= 0x1234;
unsigned char b = *(unsigned char *)&a;
在32位大端模式处理器上变量b等于?
分析
对于a,a=0x00001234,前边的0被省略掉了,那么在大端模式下,他在内存中字节的储存顺序为
00 00 12 34
*(unsigned char *)&a;这个操作就是把低地址的字节00拿出来(在上边写个代码判断当前机器是大端小端的时候就已经介绍过了)
*(unsigned char *)&a;被赋值给b,b的值就是0x00
当以“%x"形式输出16进制时,0x,和前边的0都会被省略
如果想要输出0x,就以"%#x"形式输出
2.
//X86环境,小端字节序
int main()
{
int arr[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&arr + 1);
int* ptr2 = (int*)((int)arr + 1);
printf("%x, %x", ptr1[-1], *ptr2);
return 0;
}
在小端模式下,数组元素在内存中字节的储存顺序是
对ptr1
&arr+1,对整个数组取地址加1,跳过整个数组
ptr1[-1]=*(ptr1-1)
指向如图所示,04 00 00 00,那么对应的数据就是0x 00000004,0x,和前边的0省略掉,输出4
对于ptr2
arr指向的是arr首元素地址。假设arr=0x0000EF10,将他强制转换成整数类型,整数类型加1就是单纯加个1,(int)arr+1=)0x0000EF11,跳过一个地址(字节)跳到下一个字节,这时对ptr2解引用指向的是
00 00 00 02
对应的数据是0x 02 00 00 00,输出2000000
死循环现象
这里我们在学习一个知识
我们通过这个图片可以明白,在char类型中,他的取值范围是形成一个圆环一直在循环的,(其他类型也是如此)也就是说,比如我们给char a=500,很显然,他超出了char的取值范围,可是根据这个规律,当他截断后放在char类型里边的数就是500%256=244,对应的就是-12。
再例如unsigneg char b=400,也超出了unsigned char 的范围,根据这个规律,当他截断后放在unsigned char类型里的数是400%256=144,对应的就是144
运行的结果也是如此
学习过这个之后,就可以看几道题了
1.
#include<stdio.h>
#include<string.h>
int main()
{
char arr[1000];
int u = 0;
for (u = 0; u < 1000; u++)
{
arr[u] = -1 - u;
}
printf("%d", strlen(arr));
return 0;
}
分析一波
strlen计算数组元素的个数,并且必需要碰到‘\0’才能结束,
在上边代码中,初始化arr数组,如果不考虑arr的类型,他的初始化值是从-1到-999,可是他受到char类型的限制,char类型的取值范围在-128~127,这时候就要发生截断,总之char类型会想办法让初始化的值在他的取值范围内。那么arr的初始化值就会是
-1,-2,-3,…-127,-128,-129这时候-129已经超出char的范围,char想办法让他满足自己的范围,那么根据上边学习的规律-129放在char类型里就是127,
在接下来就是127,126,125…2,1,0,-128,-127.就这样循环下去
因为’\0’对应的ASCII码是0,所以strlen会计算’\0’之前的字符个数为128+127=255
2.
int main()
{
unsigned int i = 0;
for (i = 0; i <= 255; i++)
{
printf("haha\n");
}
return 0;
}
int main()
{
unsigned int j = 0;
for (j = 12; j >= 0; j--)
{
printf("hehe\n");
}
return 0;
}
这两段代码,无一例外他们的结果最后都是死循环。
分析
对第一个
unsigned int类型的取值范围0到255,在第一个循环中,i加到266时,超出unsigned int的范围,unsigned int将他转化成符合自己范围的数值即根据规律计算出是0,这样循环条件恒成立,就陷入了死循环
同理第二段代码
当j 减到0时,再减变为-1,不满足unsigned int的取值范围,unsigned int将他转化成符合自己范围的值,根据上边学习的内容计算出时255,循环条件恒成立,陷入死循环
作者有话说:作者只是一只小白,以上解释均是作者对已学知识的理解,巩固,复习。希望可以帮到大家,如有错误,感谢指出