深度剖析数据在内存中的存储
C语言中的类型
内置类型
C语言自带的类型,使用的时候我们只要调用就好
int , char ,float ,double ...
自定义类型
- 数组类型
//定义一个数组
int arr[10]={1,2,3,4,5,6,7,8,9,10};
- 结构体类型 struct
//定义一个结构体类型
struct class
{
char mame[10];
char person[10];
}; //...
- 枚举类型 enum
enum school
{
class1,class2
//...
};
- 联合类型 union
union number
{
int x;
long y;
double z;
};
- 指针类型
int* p;
char* p;
- 空类型
void表示空类型(无类型),通常用于无返回值函数创建,无参数函数创建,指针类型(void* p,表示无类型的指针)
整型在内存中的存储
整形在内存中的存储可能是最简单的存储方式了,在内存中整型的存储方式是以二进制补码的形式来进行存储的
原码,反码,补码的基础知识
在了解原码,反码,补码的知识之前,我们可以先了解一下机器码,真值得相关概念,不过我在这里就不在赘述了。(这里统一用(±130)来讲解)
- 正数的原码
原码很简单,其实就是这个数的二进制罢了
0000 0000 0000 0000 0000 0000 1000 0010
- 正数的反码
正数的反码和补码都是一样的和原码一样,但是负数的话就不一样
- 负数的原码
负数的原码第一位表示符号位,负数用来表示
1000 0000 0000 0000 0000 0000 1000 0010
- 负数的反码
符号位不变,其他为按位取反
1111 1111 1111 1111 1111 1111 0111 1101
- 负数的补码
反码加1
1111 1111 1111 1111 1111 1111 1111 1110
有符号数(signed),无符号数(unsigned)
- 无符号数 = 正数,原码,反码,补码相同
- 有符号数 = 负数,原码,反码各不相同
整数在内存中存储的方式为二进制补码,以十六进制展示出来
大小端的概念
大端
简单点来说就是将一个数在内存中的十六进制数进行从高位到低位来存储
而在内存中规定从左向右为低————>高,二这种反过来存储的方式就被称为大端存储,我们来看一个数字应该就比较好理解了
# include<stdio.h>
int main()
{
int a = 10;
return 0;
}
按照我们的习惯来说在内存中应该是:0x00 00 00 0A
来存储的但实际上呢
我们可以看出是0a 00 00 00
这就是大端存储
小端
小端其实就是按照正常的顺序进行存储
int a = 20;//0x00 00 00 14
//大端:14 00 00 00
//低地址---------->高地址
//小端:00 00 00 14
设计一个程序判断该机器是大端还是小端
//强制类型转换
# include<stdio.h>
int main()
{
int a = 14;
char* pa =(char*)&a;
int b = *pa;
if(b == 0)
printf("小端存储");
else
{
printf("大端存储");
}
return 0;
//用强制类型转换的方式打印4个字节中的一个字节
}
char类型的存储方式
char
类型的存储方式和整型的差不多,不过是以Ascll码的形式存储的,仅仅只有一个字节罢了,不过我们需要注意一点
**字符类型中如果是一个无符号数那么它可以存储的数据范围为0~255,如果是一个有符号的话那么就会有正负,这时因为字符并没有负数这个说法,所以取值范围就为-127——128的范围内
在使用的时候我们就要小心,不然一不小心就会使程序进入死循环
整型提升
# include<stdio.h>
int main()
{
char a = -20;
//10000000 00000000 00000000 00010100原
//11111111 11111111 11111111 11101011反
//11111111 11111111 11111111 11111100补
//拿出11111100
//整型提升
//11111111 11111111 11111111 11111100补
//%u是无符号数字说明原反补是一样的
printf("%u\n",a);
return 0;
//整型提升的时候看到是char这个类型,不是%u
}
浮点数在内存中的存储
在内存中的存储形式和int类型是不一样的,规定用这种方式来计算浮点数在内存中的存储形式
(-1)^s * m * 2^e
其中(-1),表示符号位s=0,表示正数,s=1的时候表示负数,m表示转化为二进制的值用1.xxxxx表示,e表示指数为(将小数点归位)
举个例子
float = 7.5;
转化为二进制:111.1
(-1)^0 * 1.111 * 2^2
因为在使用科学计数法的时候e有可能出现负数的情况,而分配给e的内存只能存储unsigned的数,那么就规定给每一个e在存储的时候首先
加上一个127,当e超出10的时候加上一个1023
所以
s=0 , m=1.111 , e=129
转化为二进制,规定在32位机器上,分配给符号位(s)的内存为1个bit,分配给m的内存为23个bit,而e为8个bit,64位时1,11,52,书写时为s->e->m,并且1.xxxx的1不用写,只写xxxxx
**01000000111100000000000000000000
转化为16进制在内存中显示就是这个
0x 40 F0 00 00
取出来的时候
- e不为全0和全1的时候,按照存放时反过来就好
- e为全0的时候将不在遵循,直接通过 0.xxxx * 2^1-127 还原回来
- e为全1的时候…文档,这里不做过多解释因为有点复杂,哈哈哈哈,大家可以去看官方文档,不过可能有点多
来看一个题
# include<stdio.h>
int main()
{
int n = 9;
float* pa = (float*)&n;//强制类型转换为float类型,因为指针都是4个字节,所以内存里面的值没有改变
printf("%d\n",n);//打印整型,because内存里面的值没有改变,so打印的是9
printf("%f\n",n);//这里打印的是float类型的,但是内存里面的是整型,so就会按照整型的方式来打印
//0 00000000 000 0000 0000 0000 0000 1001->9的二进制
//按照float来的话e全为0
//(-1)^0 * 0.000 0000 0000 0000 0000 1001 * 2^1-127
//所以打印出来将是一个很小的数,so:0.00000000000000
*pa = 9.0;//按照float的类型存放9在内存里面
//1001.0
//(-1)^0 * 1.0010 * 2^3
//0 1000 0010 0010 0000 0000 0000 0000 000//存放形式
printf("%d\n",n);//但是这里打印的是整型,所以按照整型来计算:so:1,091,567,616
printf("%f\n",*pa);//打印的是float的类型,so可以正常输出9.0
return 0;
}
最后输出的结果和分析的是一样的
如果大家感兴趣的话还可以去研究一下其他类型的如DOUBLE的其实都差不多