目录
1.整数在内存中的存储
原码、反码、补码
符号位和数值位,0正,1负,数值位最高位是符号位,剩下数值
正数原反补都相等
原:直接按照正负号和相应的数值以二进制形式呈现
反:原码符号位不变,数值位取反
补码;反码+1是补码
整型数据存放在内存中是以补码形式存储,补码可以统一处理符号位和数值位
由于cpu只有加法,所以补码还能统一处理加法和减法,由于运算过程相同,原码和补码不需要额外的电路就可以实现
2.大小端字节序和字节序判断
一组数据,在内存中以字节为单位,倒着存储
2.1什么是大小端
超过一个字节的数据在内存中存储的时候,有存储顺序的问题
按照不同存储顺序,分大端字节序存储和小端字节序存储
大端:数据的低位字节内容保存在内存的高地址处,数据的高位字节内容,保存在内存的低地址处
小端:数据的低位字节内容保存在内存的低地址处,数据高位字节内容,保存在内存的高地址处
2.2为什么有大小端
内存的单位是字节,但1个字节只有8bit,但除了char是8bit外,其他大多都不是8bit,比如int的32bit。大于8位的处理器,寄存器宽度大于一个字节,那必然要考虑多个字节顺序的问题
举例: 16bit的short类型x 内存的起始地址是0x0010 由于是16bit,也就是两个字节 第一个字节地址是0x0010,第二个字节地址是0x0011 x的值设为0x1122 因为是16进制,2个16进制位刚好是8bit,那么高字节是0x11 低字节就是0x22 大端模式,就是高字节0x11放入地址0x0010 低字节0x22放入地址0x0011 小端模式,刚好相反。 常用x86结构式小端,keil c51是大端,许多arm和dsp是小端 有些ARM处理器是由硬件选择是大端还是小端
2.3练习
练习1
#include<stdio.h> int check_sys() { int i = 1;//用简单的数字 return (*(char*)&i);//取第一个字节的内容,如果直接用(char)i; //会发生截断,但截断针对的是有数值的部分,不管是大端还是小端,都先截断01的部分 //其他部分都不会计入 } int main() { int ret = check_sys(); if (ret == 1)//由于1只用1个字节,其他3个字节都是0 //有数值的那个字节要么是开头,要么是结尾,对应小端或大端 { printf("小端\n"); } else { printf("大端\n"); } return 0; }
练习2
char类型默认是signed(取决于编译器)也就是有符号位的
由于-1是整型类型,32bit,但char只有8bit,发生截断,从低位数据开始截断8bit
100000000000000000000000000
1111111111111111111111111111110
1111111111111111111111111111111
因此是a,b,c存的数据内容都是11111111
整型数据在内存中存储是以补码形式
%d是以十进制形式,打印有符号整型
这里需要整型提升,由于char是有符号位,且符号位是1,所以a整型提升的时候,前面补24个1,1111111111111111111111111111111,然后补码变原码
b也是同理,整型提升后也是1111111111111111111111111111111,然后补码变原码打印
c不一样,c是无符号整数,因此补0,变成000000000000000000111111111,由于是正数补码,所以原码相等,因此打印255。
如果是有符号位的,那么-1要先从补码变成原码,最终结果是-1
但如果没有符号位,那么默认是正数,那么补码-1等于255,所以结果是255
练习3
#include<stdio.h> int main() { char a = -128; printf("%u\n", a); return 0; } char类型的取值范围,如果是有符号位,则-128~127 10000000000000000000000010000000是-128原码 11111111111111111111111101111111是反码 11111111111111111111111110000000是补码 发生截断之后,a存的就是10000000,10000000是直接识别为-128 因此再以无符号整数形式打印后,先整型提升变成 11111111111111111111111110000000,然后由于是无符号 所以直接打印,也就是32位二进制数的十进制减去127等于 4294967168
#include<stdio.h> int main() { char a = 128; printf("%u\n", a);//u是无符号整数 return 0; } 首先是128的原码:00000000000000000000000010000000 反码:01111111111111111111111101111111 补码:01111111111111111111111110000000 放入char类型中发生截断,则是10000000 然后以无符号整数打印前先整型提升,变成: 11111111111111111111111110000000 然后直接打印,则是4294967168
练习4
#include<stdio.h> int main() { char a[1000]; int i; for (i = 0; i < 1000; i++) { a[i] = -1 - i; //内容从-1开始,一直-1,但是char类型的取值范围是-128~127 所以,一直-1,会变成0,由于\0的ascll码就是0 } printf("%d", strlen(a));//所以strlen(a)就是255 //另外,由于0-1又是-1,所以会继续轮回,直到遍历数组 return 0; }
练习5
#include<stdio.h> unsigned char i = 0; int main() { for (i = 0; i <= 255; i++) { printf("hello world\n"); } return 0; } //由于char无符号类型的取值范围是0~255 当i=255后执行一次,然后i++,i=256,但会溢出,高位舍掉,只剩8个0了 所以i又变成是0了,于是继续执行循坏,造成了死循环
#include<stdio.h> int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u\n", i); } return 0; } //首先无符号整型,不能放负数 所以当i=0执行一次后,i--; 按理来说是-1,但实际上0的二进制是下面 00000000 00000000 00000000 00000000 -1,那么根据进位原则,需要向第33位借2 再-1,就变成11111111 11111111 11111111 11111111 由于是按照无符号整型打印,所以,结果就是32位二进制的值 4294967295
练习6
#include<stdio.h> 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; } //这里用小端的x86 //&a+1就是跳过了整个数组,也就是4这个元素之后的地址 //将这个地址的类型从数组指针强制变成整数指针 //ptr[-1]=*(ptr1+(-1)) 由于前面已经强制转换类型了,所以这里-1,只会向前跳过一个元素 而不是整个数组 最终ptr1[-1]的值就是4 //a代表首元素地址,也就是首元素第一个字节的地址,强制转换类型int后 +1,再转换成整型指针,就是跳过了一个字节,也就是首元素第二个字节的地址 然后解引用,因为整型是4个字节,所以会涵盖第二个元素的第一个字节 又由于是小端模式,所以低字节放低地址 解引用的第一个到到第3个字节都是00,但第4个字节是02 (因为第二个元素16进制形式是0x00 00 00 02) 所以转换成16进制形式后就是0x02 00 00 00 %x就是以16进制形式形式输出整数 最终*ptr2就是2000000
3.浮点数在内存中的存储
浮点:float、double、long double
范围在float.h头文件中
IEEE754规定了二进制浮点数公式:
v=(-1)^S * M * 2^E
1.01*2^2
s决定了正负号
m是1.01,且大于等于1,小于2(因此存储的时候1是省略的,读的时候再添加)
2^E就是指数位,比如1.01*2^2=101
32位浮点数,最高位1位存S,后面8位存E,剩下23位存M(因为1省略,所以这里存的都是小数部分)
64位浮点数,最高位1位存S,后面11位存E,剩下52位存M(因为1省略,所以这里存的都是小数部分)
由于E可以是负数,但E本身存储时又是无符号整数,所以加一个中间值,就可以避免出现存储负数,32位中间值是127,64位是1023
如果e不全为0或不全为1 十进制0.5的二进制是0.1 由于正数部分必须为1,小数点向右移 1.0*2^(-1)(这是二进制形式) s是0,e是-1+127,M存储0,由于还有22位空的,补0进去 于是二进制存储时:0 01111110(是十进制127) 000000000000000000000(23个0) e存的都是0 那么e是-126(1-127)或者-1022(1-1023) 假设是0 00000000 0010000000000000 那么就表示成(如果s是1,则是1*后面的东西)0.001*2^-126 e存的都是1 真实E是128 那么是1.xx*2^128 表示+-无穷大
#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; }
9的二进制是00000000000000000000000000001001(原码/补码)
根据%d打印N是9,很正常,但如果按照%f,则要按照上面的方法来
0 00000000 0000000000000000000001001
则(-1)^0 * 0.0000000000000000000001001*2^-126==0.000000(float只打印小数点6位)
下面,以浮点数形式存储9.0
(-1)^0 * (1.001) *2^3
则是0 1000010 00100000000000000
则是直接打印0100001000100000000000000的十进制形式