目录
- 整数在内存中的存储
- 大小端字节序和字节序判断
- 浮点数在内存中的存储
1. 整数在内存中的存储
整数的二进制表示方法有三种: 原码、反码和补码
三种表示方法均有符号位和数值位两部分,符号位0表示“正”,1表示"负",而数值位最高的一位被当做符号位,剩余的都是数值位
正数的原、反、补码都相同
负数的三种都不同:
原码: 直接将数值按照正负数的形式翻译成二进制就是原码
反码:原码的符号位不变,其他位依次按位取反就可以得到反码
补码: 反码+1得到补码
对于整形来说,数据在内存中存放的是补码,原因在于补码可以吧符号位和数值域统一处理,加法和减法也可以作为加法统一处理。补码与原码互换,运算过程相同,不需要额外的硬件电路
1.1 取值范围
signed char 和 unigned char
以下列出char类型二进制所有的取值,探讨它的范围
对于有符号的char,最大值就是0111 1111,再加1就是1000 0000,如下图
从0到127,再加1就成为最小值-128,全为1时是负数最大值-1,每次递增1的情况是这样
对于无符号的char,127再加1是128,如下图
它的递增图形如下图
2. 大小端字节序和字节序判断
了解了整数存储后,看一个细节
int a = 0x11223344 ;
调试看到该数是倒着存储的
2.1 什么是大小端
超过一个字节的数据在内存中存储的时候,就有存储顺序的问题了,按不同的顺序,分为大小端存储
大端存储模式: 数据的低位字节存在高地址处,高位字节存在低地址处
小端存储模式: 数据的低位字节存在低地址处,高位字节存在高地址处
2.2 为什么有大小端之分
计算机以字节为单位,每个地址单元对应一个字节,一个字节8bit位,c语言除了8bit的char外,还有16bit的short,32位的long,另外,对于位数大于8位的处理器,如16位处理器和32位处理器,由于寄存器宽度大于一个字节,必然存在着一个如何将多个字节安排的问题,因此导致了两种存储模式
例如: 一个16bit的short型变量x,在内存中地址为0x0010,值为0x1122,那么0x11就是高字节,0x22就是低字节,大端将0x11放在低地址,0x22放在高地址,小端相反,常用的x86结构是小端模式,KEIL C51则为大端模式。很多的ARM,DSP都为小端模式,有些ARM处理器可以由硬件选择是大端还是小端
2.3 练习
2.3.1 练习1
简述大端字节序和小端字节序的概念,设计一个小程序判断当前机器的字节序 (10分)-百度笔试题
思路: 大端小端在于变量值的存储顺序不同,可以以1值举例,大端会将1存在高地址处,所以可以取出变量一个字节判断,变量地址是低地址处,为0就是大端
也可以利用联合体,给int类型赋值,联合体加一个char类型,取出char的值判断
代码:
//1
int a = 1;
if (*(char*)&a = 0) {printf("大端");
}
else {printf("小端");
}//2
union {int i;char c;
}un;un.i = 1;
printf("%d",un.c);
2.3.2 练习2
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d", a, b, c);
输出:
a和b都是有符号,存-1打印出的就是-1,c是无符号的,存到char里8个1都是数值,原码值就是最大值255
2.3.3 练习3
char a = -128 ;
printf ( “%u\n”, a) ;
char a = 128 ;
printf ( “%u\n”, a) ;
输出:
第一个char a存的是1000 0000,将它当做无符号打印,整形提升补符号位,补24个1,当做无符号整型打印,所以这个值就是很大的数
第二个,128的二进制是1000 0000,a存的是这个,因为a是有符号的,即使以无符号打印,整形提升依然补充符号位,所以和上面的结果一样
2.3.4 练习4
char a[1000] ;
int i ;
for ( i = 0; i < 1000; i++ )
{
a[i] = -1 -i ;
}
printf (“%d”, strlen (a) ) ;
}
第一次,i=0时,保存-1.i=1时,保存-2。因为数组是char类型,最多到负数最小值-128,根据有符号char的递增图,-128再减1得到127,当减到0时,strlen结束,所以长度就是128 + 127 = 255
2.3.5 练习5
unsigned char i = 0 ;
unsigned int i1;
for ( i = 0; i <= 255; i++ )
{printf ( "hello world\n" ) ;
}for ( i1 = 9; i1 >= 0; i-- )
{printf ( "%u\n", i ) ;
}
上述两个都是死循环,第一个无符号char的取值范围是0-255,所以<=255恒成立,第二个无符号数不可能小于0,所以>=0恒成立
2.3.6 练习6
int a[4] = { 1, 2, 3, 4 } ;
int* ptr1 = (int *)(&a + 1) ;
int* ptr2 = (int *)((int)a + 1) ;
printf("%x,%x", ptr1[-1], *ptr2) ;
假设a的地址是19ff20
ptr1取的是整个数组的地址,+1是跳过整个数组.是图中所示19ff30,,转化为int指针,访问-1就是往后减一个4字节,19ff2c,取整形内容,就是4
将a转换为整形,+1就是原地址加一个字节,图中所示19ff21,转换为int指针,访问值取四个字节,就是,19ff21-19ff25,小端存储由高到低排列十六进制就是00020000,
输出:
3. 浮点数在内存中的存储
先思考一下下面代码的输出结果:
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);
输出:
3.2 浮点数的存储
上面的num和*pFloat存的都是一个数,为什么读取的结果差距这么大,这说明整形和浮点数的存储和读取方式是不一样的
3.2.1 浮点数存的过程
根据国际标准IEEE(电器和电子工程协会)754,任意一个二进制浮点数都可以表示成下面的形式
V = (-1)S * M * 2E
(-1)S表示符号位,当S=0时,V为正数,S=1,V为负数
M表示有效数字,M是大于等于1,小于2的
2E表示指数位
举例5.5,用二进制表示为101.1
相当于1.011 × 22
按照上面的V格式,S=0,M=1.11,E=2
IEEE 754规定:
对于32位浮点数,最高的1位存储符号位S,接着的8为存储指数位E,剩下的23位存储有效数字M
对于64位浮点数,最高的1位存储符号位S,接着的11为存储指数位E,剩下的52位存储有效数字M
因为,1<M<2,所以M的5.5的二进制101.1写成1.xxxx的形式
计算机存储浮点数时,默认M第一位总是1,因此1可以舍去,只存1小数点后面的数字,读取的时候再补上1和小数点,就可以多存一位数字
至于指数E,比较复杂
规定E为一个无符号整数 (unsigned int)
这意味着,如果E为8位,取值为0-255,E为11位,取值为0-2047。但是,科学计数法中的E可能是负数,所以E必须加一个中间值,8位中间数就是127,11位就是1023,比如E为10,保存时加上127,就是137
所以,存的二进制就是,符号位S:0,E指数位127+2=129,M寸011,后面补0
小尾排列就是00 00 B0 40
3.2.2 浮点数取的过程
分为三种情况:
E不全为0或不全为1
这时,浮点数用下面的规则表示,指数E减去127(或1023),得到真实值,再将有效位M前加上第一位的1
比如上面的5.5,指数位E转换为十进制是129,减去127就是2,M补上1.,就是1.011,乘2的2次方,就是101.1,转换为十进制就是5.5
E全为0
这时,浮点数的指数E等于1-127(或者1-1023,即为真实值),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数,这样做表示±0,以及接近于0的很小的数字
E全为1
这时,如果有效数字M全为0,表示±无穷大,(正负取决于符号位s)
3.3 题目解析
现在来看上面的题目
首先第一个,整数n存为9,打印的仍是9,将存的9以浮点数取出,就是
00000000 00000000 00000000 0000009,指数位全为0,就是一个接近于0的很小的数字,就是,所以值为0
将浮点数9.0按浮点数的方式存下来
0 1000 0010 00100000 00000000 0000000
0100 0001 0001 0000 0000 0000 0000 0000 //四位一排
按整数取出来,数据位就是0后面的部分,浮点数的形式取出依旧是浮点数的9.0
后面的整数值如下:
输出: