目录
在了解整数在内存中的存储方式之前,我们先要了解二进制。
其实我们经常能听到2进制、8进制、10进制、16进制这样的讲法,那是什么意思呢?其实2进制、8进制、10进制、16进制是数值的不同表⽰形式⽽已。
我们重点介绍⼀下⼆进制:
⾸先我们还是得从10进制讲起,其实10进制是我们⽣活中经常使⽤的,我们已经形成了很多尝试:
• 10进制中满10进1
• 10进制的数字每⼀位都是0~9的数字组成
其实⼆进制也是⼀样的
• 2进制中满2进1
• 2进制的数字每⼀位都是0~1的数字组成
例如 1101 就是⼆进制的数字。
其实10进制的123表⽰的值是⼀百⼆⼗三,为什么是这个值呢?其实10进制的每⼀位是有权重的,10进制的数字从右向左是个位、⼗位、百位....,分别每⼀位的权重是 10^0 , 10^1 , 10^2 ...
如下图:
2进制和10进制是类似的,只不过2进制的每⼀位的权重,从右向左是: 2^0 , 2^1 , 2^2 ...
如果是2进制的1101,该怎么理解呢?
• 8进制中满8进1
• 8进制的数字每⼀位都是0~7的数字组成
•在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀
个8进制位,剩余不够3个2进制位的直接换算
如:2进制的01101011,换成8进制:0153,0开头的数字,会被当做8进制
↵ ↵
• 16进制中满16进1
• 16进制的数字前10位都是0~9的数字组成,后6位由a~f的字母代替数字组成
• 在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算
如:2进制的01101011,换成16进制:0x6b,16进制表⽰的时候前⾯加0x
到这里,二进制就介绍完了
下面,我们来介绍整数的2进制表⽰⽅法
整数的2进制表⽰⽅法有三种,即原码、反码和补码
三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,⽽数值位最⾼位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
无符号的整数(>=0),没有符号位;32位都是数值位
负整数的三种表⽰⽅法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
计算的时候按照补码来计算和操作,打印出来的是原码,所以需要补码先取反再+1
当我们了解了整数在内存中存储后,我们调试看⼀个细节:
- #include
- int main()
- {
- int a = 0x11223344;
-
- return 0;
- }
我们发现,这个数字是按照字节为单位,倒着存储的。这是为什么呢?
我们存储数据有3种方法:
由于我们存进去的,我们就会怎么拿出来,所以乱序存储方法不合适
我们就采用前面2种。
数字是倒置存储的,数据在内存中存储的方式是二进制,但是在vs的内存窗口上展示的是16进制
其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念:
⼤端(存储)模式:是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。
⼩端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。
为什么会有⼤⼩端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为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处理器还可以由硬件来选择是⼤端模式还是⼩端模式。
请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。
我们以1举例,判断编译器的存储方式(博主的是VS2020),我们只需要知道第一个字节是不是0就可以判断使大端还是小段了。
- #include
- int main()
- {
- int a = 1;
- if ((*(char*)&a) == 1)
- {
- printf("小端\n");
- }
- else
- printf("大端\n");
- return 0;
- }
- #include
- int check_sys()
- {
- int a = 1;
- return (*(char*)&a);
- }
- int main()
- {
- int a = 1;
- if (check_sys() == 1)
- {
- printf("小端\n");
- }
- else
- {
- printf("大端\n");
- }
- return 0;
- }
- #include
- 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;
- }
输出结果是什么?
- #include
- int main()
- {
- char a= -1;//char类型8个比特位
- //10000000 00000000 00000000 00000001---原码
- //11111111 11111111 11111111 11111110---反码
- //11111111 11111111 11111111 11111111---补码
- //截断--->11111111---a
- //整型提升---以符号位为依据进行提升,无符号数,高位补0
- //11111111 11111111 11111111 11111111---补码
- //10000000 00000000 00000000 00000000---反码
- //10000000 00000000 00000000 00000001---原码
- //-1---a
- signed char b=-1;//有符号整型
- //10000000 00000000 00000000 00000001---原码
- //11111111 11111111 11111111 11111110---反码
- //11111111 11111111 11111111 11111111---补码
- //截断--->11111111---b
- //11111111 11111111 11111111 11111111---补码
- //10000000 00000000 00000000 00000000---反码
- //10000000 00000000 00000000 00000001---原码
- //-1---b
- unsigned char c=-1;
- //10000000 00000000 00000000 00000001---原码
- //11111111 11111111 11111111 11111110---反码
- //11111111 11111111 11111111 11111111---补码
- //截断--->11111111---c
- //00000000 00000000 00000000 11111111---补码/反码/原码(>0)
- //255---c
- printf("a=%d,b=%d,c=%d",a,b,c);//%d--打印10进制的有符号整型
- return 0;
- }
- #include
- int main()
- {
- char a = -128;
- printf("%u\n",a);
- return 0;
- }
- #include
- int main()
- {
- char a = 128;
- printf("%u\n",a);
- return 0;
- }
输出分别是什么?
- #include
- int main()
- {
- char a = -128;//有符号的char类型,高位是符号位
- //10000000 00000000 00000000 10000000---原码
- //11111111 11111111 11111111 01111111---反码
- //11111111 11111111 11111111 10000000---补码
- //截断--->10000000---a
- //整型提升
- //11111111 11111111 11111111 10000000---原码/反码/补码
- //打印无符号整型---> >=0
- printf("%u\n",a);//%u打印无符号整型
- return 0;
- }
- #include
- int main()
- {
- char a = 128;//有符号的char类型,高位是符号位
- //00000000 00000000 00000000 10000000---原码
- //01111111 11111111 11111111 01111111---反码
- //01111111 11111111 11111111 10000000---补码
- //截断--->10000000---a
- //整型提升
- //11111111 11111111 11111111 10000000---原码/反码/补码
- //打印无符号整型---> >=0
- printf("%u\n",a);
- return 0;
- }
- #include
- int main()
- {
- char a[1000];
- int i;
- for(i=0; i<1000; i++)
- {
- a[i] = -1-i;
- }
- printf("%d",strlen(a));
- return 0;
- }
输出结果是什么?
求字符串长度,统计的是\0之前出现的字符个数,\0的ASCLL码值是0,所以只要找到0就好
我们来看看char类型的取值范围:
- #include
- int main()
- {
- char a[1000];
- int i;
- for(i=0; i<1000; i++)
- {
- a[i] = -1-i;
- //-1,-2,-3......-128,127,126......0
- //反着转圈
- }
- printf("%d",strlen(a));//255
- return 0;
- }
- #include
- unsigned char i = 0;
- int main()
- {
- for(i = 0;i<=255;i++)
- {
- printf("hello world\n");
- }
- return 0;
- }
-
- #include
- int main()
- {
- unsigned int i;
- for(i = 9; i >= 0;i++)
- {
- printf("%u\n",i);
- }
- return 0;
- }
- #include
- unsigned char i = 0;//无符号char:0~255
- int main()
- {
- for(i = 0;i<=255;i++)
- {
- printf("hello world\n");//死循环
- }
- return 0;
- }
- #include
- int main()
- {
- unsigned int I;//无符号char:0~255;>=0恒成立
- for(i = 9; i >= 0;i++)
- {
- printf("%u\n",i);
- }
- return 0;
- }
- #include
- 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;
- }
输出结果是什么?
添加#,打印16进制
常⻅的浮点数:3.14159、1E10等,浮点数家族包括: float、double、long double 类型。
浮点型和整型数据在内存中的存储形式和取出方式都不同
我们可以通过练习来看看:
- #include
- int main()
- {
- int n = 9;
- float *pFloat = (float *)&n;
- printf("n的值为:%d\n",n);
- printf("*pFloat的值为:%f\n",*pFloat);
- *pFloat = 9.0;
- printf("num的值为:%d\n",n);
- printf("*pFloat的值为:%f\n",*pFloat);
- return 0;
- }
上⾯的代码中, num 和 *pFloat 在内存中明明是同⼀个数,为什么浮点数和整数的解读结果会差别这么⼤?
根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V = (−1) ^S ∗ M ∗ 2^E
• (−1)S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
• M 表⽰有效数字,M是⼤于等于1,⼩于2的
• 2^E 表⽰指数位
IEEE 754规定:
对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
前⾯说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表⽰⼩数部分。在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的xxxxxx部分。
E为⼀个⽆符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围0~2047。
科学计数法中的E是可以出现负数的,所以IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1。
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值(E+127(1023)之后为0),有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字。
E全为1
这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s)
- #include
- int main()
- {
- int n = 9;//整型存储方式,补码
- //00000000 00000000 00000000 00001001---原码/反码/补码
-
- float *pFloat = (float *)&n;
- printf("n的值为:%d\n",n);//9
- printf("*pFloat的值为:%f\n",*pFloat);
- //0 00000000 00000000000000000001001
- //E为全0
- //(-1)^0 * 0.000000000000000001001 * 2^ -126
- //接近0
- //%f只打印前6位
- //0.000000
- *pFloat = 9.0;
- //1001.0
- //(-1)^0 * 1.001 * 2^ 3
- //S=0
- //M=1.001
- //E=3---->3+127=130
- //01000001000100000000000000000000
- printf("num的值为:%d\n",n);
- printf("*pFloat的值为:%f\n",*pFloat);//9.0
- return 0;
- }
本次的分享到这里就结束了!!!
PS:小江目前只是个新手小白。欢迎大家在评论区讨论哦!有问题也可以讨论的!
如果对你有帮助的话,记得点赞👍+收藏⭐️+关注➕