目录
一.回顾:整数在内存中的存储
二.大小端字节序和字节序的判断
什么是大小端 ?
为什么需要有大小端之分呢?
判断当前机器的字节序
三.练习
四.浮点数在内存中的存储
数字M
数字E
浮点数取的过程
E不全为0或者E不全为1
E全为0
E全为1
一.回顾:整数在内存中的存储
整数的二进制表示方式有三种:即原码,反码,补码。
三种表示方式均有符号位和数值位两个部分 ,符号位用0表示正,用1表示负,二进制的最高位被当作是符号位,剩余的就是数值位。
正整数的原,反,补码都相同。
负整数的三种方式不同。
- 原码:直接将数字按照正负数的形式翻译成二进制得到的就是原码。的
- 反码:符号位不变,其他位依次按位取反就可以得到反码
- 补码:反码加1得到的就是补码。s
对于整数来说:数据存放在内存中的是补码。
(详细的讲解可以看我的另一个文章:http://t.csdnimg.cn/LIINU)
二.大小端字节序和字节序的判断
当我们了解了整数在内存中的存储后,我们调试看一个细节:
#include<stdio.h>
int main()
{
int n = 0x11223344;
return 0;
}
运行这个代码,然后我们再进调试。0x+数字,表示这个数字是以十六进制写的。
int类型是4个字节,32个比特位,我们知道一个十六进制的数字可以换算成4个二进制的数字。这里0x11223344,刚好就八位十六进制,也就是32位二进制数据,完全占据这个int。
并且每两个数字结合在一起占据一个字节。也就是11占一个字节,22占一个字节......
在调试时,我们通过内存和监视观察这个int。
我们会发现在n这个整形中,数据的存储也是44 33 22 11这样的数字。但是我们发现
数据是这样进行存储的,倒着进行存储的,int的四个字节中的第一个字节存储的不是11而是44.
比如这样:
什么是大小端 ?
当数据在内存中的大小超过一个字节的时候,这时候就有了存储顺序的问题,根据不同的存储顺序,分为了大端字节序存储和小端字节序存储。
- 大端字节序存储:一个数据的低位字节的内容保存在高地址处,而高位字节的内容保存在低地址处,我们就将其称为大端字节序存储
- 小端字节序存储:一个数据的低位字节的内容保存在低地址处,而高位字节的内容保存在高地址处,我们就将其称为小端字节序存储
为什么需要有大小端之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit 位,但是在C语⾔中除了8 bit 的 char 之外,还有16 bit 的 short 型,32 bit 的 long 型(要看
具体的编译器),另外,对于位数⼤于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度⼤于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了⼤端存储模式和⼩端存储模式。
判断当前机器的字节序
#include<stdio.h>
int check_sys()
{
int n = 1;
return *((char*)&n);
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
1的十六进制是0x00000001,如果机器是小端存储,那么int的第一个字节存储的就是01,不然就是00;根据这个我们就可以判断这个机器是什么字节序存储。
三.练习
#include <stdio.h>
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;
}
%d是以十进制打印的。
char a = -1;
//10000000 00000000 00000000 00000001 -1原码
//11111111 11111111 11111111 11111110 -1反码
//11111111 11111111 11111111 11111111 -1补码
//char只有八个bit位
//11111111 补
//10000000 反
//10000001 原
//还是-1
signed char b = -1;
//char就是signed char所以更上面一样。
unsigned char c = -1;
//11111111
//因为是无符号的,所以原反补相同。
//二进制的1111 1111是十进制的255
//还有一种理解就是unsigned char的取值范围是0-255.
//-1就是0-1,所以就是255
//如果是-2,就是254
//同理如果是256就是0
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
char a = -128;
//10000000 00000000 00000000 10000000
//11111111 11111111 11111111 01111111
//11111111 11111111 11111111 10000000
//只留8个bit位
//1000 0000 补码 --- a
//char类型的数据要发生整形提升,有符号位补符号位
//11111111 11111111 11111111 10000000
//%u的形式打印,是认为a中存放的是无符号数
打印结果也就是4294967168.
#include <stdio.h>
int main()
{
char a = 128;
//0000 0000 0000 0000 0000 0000 1000 0000
//只有8个
//1000 0000 --- a
//1111 1111 1111 1111 1111 1111 1000 0000 整形提升
//%u以无符号整形十进制打印
printf("%u\n", a);
return 0;
}
结果和上面那个一样的。
四.浮点数在内存中的存储
浮点数家族包含:float,double,long double类型。
浮点数表示范围:float.h中定义
首先我们先来观察一段代码:
#include <stdio.h>
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;
}
n的值是9,所以第一行打印了9.通过*我们将float所指向的空间的值改为了9.0,所以*float是9.0.
而其他两个为啥是这样的情况呢?
很明显float类型数据在内存中的存储是与整数不同的。
根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
十进制的5.0,写成二进制是101.0,相当于1.01 * 2 ^ 2
那么按照上面的规则S = 0 ,E = 2,M = 1.01;
十进制的-5.0,写成二进制是-101.0。相当于-1.01 * 2 ^ 2
那么按照上面的规则S = 1 ,E = 2, M = 1.01
对于浮点数来说,他们在内存中的存储并不是纯的二进制,而是S E M.
对于32位的浮点数来说,最高的一位存储符号位S,接着的8位存储E,最后的23 位存储M
对于64位的浮点数来说,最高的一位存储符号位S,接着的11位存储E,最后的52位存储M.
数字M
前面说过M的取值范围是[1,2)。也就是所M可以写成1.xxxxxxxx的形式,其中xxxxxxxx表示小数部分。在计算机保存M的时候,默认第一个数总是1,因此我们可以舍去,只保留后面的小数部分。在读取的时候,我们再加上1,这样的目的是为了节省一位,这样就可以多保留一个数字了。
数字E
首先这个规定,E是一个无符号整形的数字。这意味着E如果是8位,取值范围就是0-255,E如果是11位取值范围就是2047,但是我们只科学计数法中E是可以出现负数的。所以规定再存入E的值的时候必须加上一个中间数,对于8位的E来说这个中间数是127,对于11位的E来说这个数是1023
浮点数取的过程
指数E从内存中的取出还可以分为三种情况
E不全为0或者E不全为1
这时候就采取 指数E的值减去127(或者1023)得到真实值,再将有效数字M前加上一位1
0.5的存储
0.5 十进制
0.1 二进制
1.0 * 2 ^-1
S = 0
E = -1
M = 1.0
存入E的时候就是(-1 + 127) = 126
表示为0111 1110
所以0.5的二进制表达方式为0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(1-1023)即为真实值,有效数字M不在加上第一位的1,而是还原为0.xxxxx这样的小数,这样的数字写出来是为了表示+-1(取决于符号位),这样的数字无限接近于0.
E全为1
这时,如果有效数字M不全为0,表示+-无穷大(取决于符号位)。
5.5在内存中的存储
int main()
{
//5.5
float f = 5.5f;
//5.5
//101.1
//1.011*2^2
//(-1)^0 * 1.011 * 2^2
//S=0
//M=1.011
//E=2
//2+127=129
//0 10000001 01100000000000000000000
//01000000 10110000 00000000 00000000
//40 B0 00 00
//
return 0;
}
回到我们一开始看到题目。
#include <stdio.h>
int main()
{
int n = 9;
// 00000000 00000000 00000000 00001001
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
//以浮点数的视角读取的话
//0 00000000 00000000000000000001001
//E为全0,所以这是一个无限接近0的数字。
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
//1001.0
//1.001 * 2 ^ 3
//S = 0
//E = 3
//M = 1.001
//0 1000 0010 00100000000000000000000
printf("num的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}