一、整数在内存中的存储
1.这个简单理解,整数在内存中以补码的形式在内存中存储的。
原码:将数字直接转换成二进制形式
反码:有符号数,符号位(最高位)不变,其它位取反。
补码:反码+1即可。
还有一点,补码变原码(补码的符号位不变,其它位取反,之后加1得到原码)
2.在内存中存放补码的原因(重要)
当每次写到硬件的知识的时候,我感到力不从心,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
二、大小端字节序问题
1.这个知识点简单,但是重要,大小端字节序问题的本质是解决超过一个字节大小的数据怎么存的问题,都知道,整数是以补码的形式存放的,那么一个字节是8位,怎么存放超过8位的数据呢?这是一个问题,把超过8位的数据按字节,顺序怎么存,分为大端字节序存储和小段存储,下面说说这两种存储方式!
大端存储模式:高地址的字节存低位
小端存储模式:低地址的字节存低位
VS2022代码验证:小端模式
二、为什么有大小端
这个我说了,就是解决超过1个字节的数据,怎么存数据,怎么取数据的问题!下面看看官方解释!!
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为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处理器还可以由硬件来选择是⼤端模式还是⼩端模式。
写代码,判断系统的大小端
int check_sys()
{
int a = 1; //0x 00 00 00 01
//如果小端第一个字节存的是01
return *(char*)(&a);
}
int main()
{
char ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
三、一些重要的题(难)
1.第一个题:
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
//先看看-1本身在内存中是怎么存的?
//10000000 00000000 00000000 00000001 原码
//11111111 11111111 11111111 11111110 反码
//11111111 11111111 11111111 11111111 补码
//11111111 - 在内存中1
printf("a = %d\nb = %d\nc = %d\n", a, b, c);//-1 -1 255
//取出来是以%d的形式打印
//但是在内存中是8位
//所以要整形提升(按自身类型提升)
//11111111 11111111 11111111 11111111 补码(a)
//...
//10000000 00000000 00000000 00000001 原码 (a)
//a = -1 b = -1(跟a一样)
//00000000 00000000 00000000 11111111(整型提升后的c)
//c是正数,原/反/补一样
//c = 2^8-1 = 255
return 0;
}
2.第二个题:
在这儿说说8位,在内存中以补码的形式存的数的规律.
对于unsigned char类型,(00000000~11111111)就是这个范围,( 0 ~255)这个范围,有个规律,最大值+1 = 最小值//最小值-1 = 最大值(这个规律特别重要)比如:char a;255+1 = 0 0-1 = 255,,对于signed char对于有符号类型(注意是补码)(00000000~011111111)<==> (0~127),(1000000 ~ 11111111) < = =>(-128~-1).
所以signed char的范围(-128~127)
10000000(补码)
11111111(反码)
100000000(原码)(超标了,直接解析成-128,这个在搭建电路的时候又是一个问题)
10000001(补码)
11111110(反码)
11111111(原码)(-127)
…
11111110(-2)
11111111(-1)
3:下面看题
int main()
{
char a = -128;
//-128在内存中的样子
//10000000 00000000 00000000 10000000 - 原码
//11111111 11111111 11111111 01111111 - 反码
//11111111 11111111 11111111 10000000 -补码
//10000000
printf("%u\n", a);//4294967168
//整型提升按自身类型提升
//11111111 11111111 11111111 10000000 %u
//4294967168
return 0;
}
3.第三个题:
4.第四个题:
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
//这个题是char a[1000](-128~127)
a[i] = -1 - i;
//-1 -2 -3.. -128 127 126 ... 1 0
//128+ 127 = 255
}
printf("%d", strlen(a)); //255
return 0;
}
5.第五个题:
int main()
{
unsigned char i = 0; //i==>[0,255]
for (i = 0; i <= 255; i++)
{
//死循环
printf("hello world\n");
}
return 0;
}
6.第六个题:
int main()
{
unsigned char i = 0;
for (i = 10; i >= 0; i--)
{
printf("%u\n", i);//死循环
getchar();
}
return 0;
}
7.第七个题:
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;
}
四、浮点数在内存中的存储(不是很难,掌握方法即可,重要)
1.先解答一个例题:
int main()
{
int n = 9;
//00000000 00000000 00000000 00001001
//0x 00 00 00 09
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);//9
printf("*pFloat的值为:%f\n", *pFloat); //0
//0 00000000 00000000000000000001001
//符号位 指数位 M-1
// 指数位为:00000000
// E = -126
//这儿取的时候,是按%f的形式取得转换方法
//(-1)^0*(0.00000000000000000001001)*2^(-126) == 0
*pFloat = 9.0;
//把9.0存在pFloat指向的空间
//1001.0 ==》 1.001*2^3
//符号位0 M(001) E = 127+3 = 130(10000010)
// 0 10000010 00100000000000000000000
// 01000001000100000000000000000000
// 01000001 00010000 00000000 00000000
//0x 41 10 00 00
printf("num的值为:%d\n", n); //1091567616
printf("*pFloat的值为:%f\n", *pFloat); //9
return 0;
}
这个题很重要,在这解析一下,
int main()
{
int n = 9;
//下面是9在内存中的值
//00000000 00000000 00000000 00001001
//0x 00 00 00 09
float* pFloat = (float*)&n;
printf(“n的值为:%d\n”, n);//9
printf(“*pFloat的值为:%f\n”, *pFloat); //0
//0 00000000 00000000000000000001001
//符号位 指数位 M-1
// 指数位为:00000000
// E = -126
//这儿取的时候,是按%f的形式取得转换方法
//(-1)^ 0 * (0.00000000000000000001001)*2^(-126) == 0
*pFloat = 9.0;
//把9.0存在pFloat指向的空间
//1001.0 ==》 1.001 * 2^3
//符号位0 M(001) E = 127+3 = 130(10000010)
// 0 10000010 00100000000000000000000
// 01000001000100000000000000000000
// 01000001 00010000 00000000 00000000
//0x 41 10 00 00
printf(“num的值为:%d\n”, n); //1091567616
printf(“*pFloat的值为:%f\n”, *pFloat); //9
return 0;
}
2.下面说说浮点数在内存中是具体怎么存储的!
先按我自己的理解说说一般的浮点数在内存中是怎么存储的,所谓的浮点数存储问题,就是解决了怎么存数据,怎么取数据的问题了!列如:float a = 5.5f,在内存中的存储方法,每一个浮点数都要写成这样一个形式 V = (-1)^s * M * 2^E ,只要在内存中存储s,M,E这三个数据就可以了,那么a = 5.5f < == > 101.1写成(-1)^0 *1.011 * 2^2,那么,s = 0,M = 1.011,E = 2.
S = 0,直接在第一位存0即可!
M = 1.011,由于M一定可以写成1. …,那么1就不存储,只在内存中存储小数点后面即可,因此后面的23位 01100000000000000000000(23位)
E = 2,这儿E有时候可能是个负值,所以要加127来矫正,所以存的是127+2 = 129(10000001)来存储.
所以存的是:01000000 10110000 00000000 00000000
40 B0 00 00,
下面查看内存!(小端存储模式)
3.下面看看官方解释!
上⾯的代码中, num 和 *pFloat 在内存中明明是同⼀个数,为什么浮点数和整数的解读结果会差别
这么⼤?
要理解这个结果,⼀定要搞懂浮点数在计算机内部的表⽰⽅法。
根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V = (−1) ∗ S M ∗ 2E
• (−1)S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
• M 表⽰有效数字,M是⼤于等于1,⼩于2的
• 2
E 表⽰指数位
举例来说:
⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上⾯V的格式,可以得出S=0,M=1.01,E=2。
⼗进制的-5.0,写成⼆进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
IEEE 754规定:
对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M.对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M.
4.存是取得逆过程,对于一般形式怎么存的,怎么还原,对于特殊情形这儿在讨论一下!!!
(1)E为0问题的处理情况
解:0 00000000 00100000000000000000000
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字
(2)E为0问题的处理情况
解: 0 11111111 00010000000000000000000
这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s),因为是256-127 = 129,大概是1. … *2^129表示无穷大!!!
完结!!!