剖析数据在内存中的存储

深度剖析数据在内存中的存储
本章重点
  • 数据类型详细介绍
  • 整形在内存中的存储,原码,反码,补码
  • 大小端字节序介绍及判断
  • 浮点型在内存中的存储解析

数据类型介绍

大致分为两类

内置类型

char int short float long double…

自定义类型

数组 结构体 枚举 联合体

#include<stdio.h>
int main()
{
	//数组也是一种自定义类型
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr));                //40
	printf("%d\n", sizeof(int [10]));          //40
	int a = 10;
	sizeof(int);  //可以的
	sizeof(a);    //可以的
	//那么问题来了,数组的类型是什么呢?
	//数组的类型,就是数组去掉数组名剩下的部分
	//比如上述对数组的声明 int arr[10],那么数组的类型就是int [10]
}

后续再进行介绍

基本的内置类型
内置类型又可以细分为整形和浮点型
char          //字符数据类型
short        //短整型
int          //整形
long         //长整型
long long    //更长的整形
float        //单精度浮点型
double       //双精度浮点型

char占一个字节,并不是说char占有一个字节,是char所声明的变量占据一个字节的大小,类型是不会占据空间大小的。

指针类型
int *pi;
char *pc;
float *pf;
void *pv;
空类型
void表示空类型(无类型)
通常用于函数的返回值类型,函数的参数,指针类型
#include<stdio.h>
void test()
{
	printf("hehe");
}
int main()
{
	test(20);
	return 0;
}
//屏幕上打印的结果是hehe,虽然test函数实际上是没有参数的,但是在主函数调用test函数时候,还是传了参数,但是是没有任何影响的。
//你传是你的事情,我接不接受是我的事情。
//但是如果没有参数的话,最好还是在test函数的括号中加一个void,声明这个函数是没有参数的。
整形在内存中的存储
#include<stdio.h>
int main()
{
	int a = 0x11223344;
	//0x11223344放在a里面是刚刚好的
	//因为11是一个字节,22是一个字节,33是一个字节,44是一个字节
	//通过监视看:转换到16进制,观察到a的值为0x11223344
	//通过内存看:输入&a,可以观察到,a显示为44332211
	return 0;
}

整数在内存中的存储首先要了解原码,反码,补码。

三种表示方法均有符号位和数值位,符号位都是用0表示正,用1表示负,而数值位,三种表示方法各不相同

原码:直接将二进制按照正负的形式翻译成二进制就可以了
反码:将原码的符号位不变,其他位按位取反就可以得到了
补码:反码加1是补码
#include<stdio.h>
int main()
{
	int a = -10;
	//10000000000000000000000000001010  --原码
	//11111111111111111111111111110101  --反码
	//11111111111111111111111111110110  --补码
	//fffffff6               ---补码转换成16进制的样子
	//但是在内存中&a显示的结果是f6ffffff
}
为什么在内存中存的是补码?
#include<stdio.h>
int main()
{
	//加入说我想算一个1-1的结果是多少
	//但是遗憾的是,计算机不能算减法
	//那我只好把减法转换为加法的形式
	//1 - 1;
	1 + (-1);
	//如果按原码来计算的话
	// 1的原码
	//00000000000000000000000000000001
	//-1的原码
	//10000000000000000000000000000001
	//两个码相加的结果为
	//10000000000000000000000000000010
	//如果按照原码直接相加的话,结果就为-2,这显然是不正确的
	//所以科学家们推出了补码的概念
	//用补码来算的时候,可以算出正确的结果
}

针对于为什么存的是补码

比较专业的解释是:在计算机系统中,数值一律用补码来表示都和存储。原因在于。使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法处理器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
(用补码可以把数值位和符号位一起来运算,就不牵扯纠结到底最高位要不要加的问题了)

解释为什么在内存中数字是到着存的?

如下所示,看的出来数字在内存中是到这存储的,那么问题来了,为什么是到着存储的

#include<stdio.h>
int main()
{
	int a = 0x11223344;
	//0x11223344放在a里面是刚刚好的
	//因为11是一个字节,22是一个字节,33是一个字节,44是一个字节
	//通过监视看:转换到16进制,观察到a的值为0x1223344
	//通过内存看:输入&a,可以观察到,a显示为44332211
	return 0;
}

在这里插入图片描述

大端字节序 11223344
小端字节序 44332211
对于0x11223344来说,11是他的高位,44是他的低位
大小端介绍
什么是大小端?

大端:把一个数据的低位字节序的内容存储到高地址处,高位字节序的内容存储到低地址处。(是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中)

小端:把一个数据的低位字节序的内容存储到低地址,高位字节序的内容存储到高地址(是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中)

百度:

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序

//解题思路:
//我们可以通过把1存在内存中,然后通过看他的存储形式,我们来判断当前机器到底是大端还是小端
//1的16形式的格式为00000001
//如果存储形式是01000000那么就是小端
//如果存储形式是00000001那么就是大段
//我们可以通过读取第一个字节的内容,来判断到底是大端还是小端,想要读取一个整形的第一个字节,那么存储在char*类型的变量中就可以了。
#include<stdio.h>
int main()
{
	int a = 1;
	char* pa = (char*)&a;
	if (*pa == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}

//封装成一个函数
#include<stdio.h>
int check_system()
{
	int a = 1;
	char* pa = (char*)& a;
	if (*pa == 1)
		return 1;
	else
		return 0;
}
int main()
{
	int ret = check_system();
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}
联合体(共用体)
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
};
union Un
{
	int i;
	char c;
};    //联合体类型
int main()
{
	union Un u = { 0 };   //u为联合体对象
	printf("%d\n", sizeof(u));   //结果为4
	printf("%p\n", &u);
	printf("%p\n", &(u.i));
	printf("%p\n", &(u.c));    
	//三个的地址是一模一样的,说明i和c占用同一块空间
	//所以联合体又叫共用体,共用体的意思就是我的成员要共用同一块空间
	//那么我给i里面放东西c就会发生变化
    //我给c里面放东西,i也会发生变化
    //c是i占的四个字节中的第一个字节,这也解决了大小端算法的另一种解法问题
	return 0;
}
判断大小端的另一只种解法(利用联合体)
#include<stdio.h>
int check_system()
{
	union Un
	{
		int i;
		char c;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = check_system();
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}
练习题
#include<stdio.h>
int main()
{
	char a = -1;
	signed char b = -1;
	//char和signed char其实是一样得,所以前两个得结果一定是一样的
	//-1的补码为:11111111111111111111111111111111
	//保存到char中为11111111
	//反码为:11111110
	//原码为:10000001  --所以说前两个的结果为-1 和 -1
	//有符号的类型,高位所需要补的是符号位
	unsigned char c = -1;
	//c放在内存中 11111111---补码
	//又因为c是无符号数,所以给c的高位补0
	//00000000000000000000000011111111  -- 补码
	//又因为最高位是0,所以说为正数,正数的原,反,补相同
	//所以c的结果为255  8个1的结果为255
	printf("a=%d,b=%d,c=%d", a, b, c);
	//-1 -1 255
	return 0;
}
#include<stdio.h>
int main()
{
	char a = -128;
	//10000000000000000000000010000000   原码-- -128
	//11111111111111111111111101111111   反码
	//11111111111111111111111110000000   补码
	//但是只能存8个比特位,结果为10000000
	//需要进行整形提升
	//提升的时候需要看原来的符号位,全部补成1
	//结果为11111111111111111111111110000000  --补码
	//又因为是无符号数,所以原码,反码,补码全部相同
	//所以就得到了结果:4294967168
	printf("%u\n", a);
	return 0;
}
#include<stdio.h>
int main()
{
	char a = 128;
	printf("%u\n", a);
	return 0;
}
//char的范围是-128~+127,所以char是无法表示+128的
#include<stdio.h>
int main()
{
	int i= -20;
	unsigned  int  j = 10;
	printf("%d\n", i+j); 
	//10000000000000000000000000010100
	//11111111111111111111111111101011
	//11111111111111111111111111101100
	//00000000000000000000000000001010
	//11111111111111111111111111110110
	//11111111111111111111111111110101
	//10000000000000000000000000001010
	//-10
	return 0;
}
#include<stdio.h>
int main()
{
	unsigned int i;
	//无符号的数,是没有负数的
	//判断条件为i>=0,但是无符号的,i>=0是永恒成立的
	//所以结果为死循环
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}
#include<stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	//什么时候遇到\0,长度就是多少,参考char的范围的哪个圆圈
	//所以结果为255
	return 0;
}
#include<stdio.h>
unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
	//这个程序会死循环
	//unsigned char 的范围就是0-255,所以会死循环
}
浮点型在内存中的存储

常见的浮点数:

3.1415 1E10(为科学计数法,表示的是1.0*10^10),浮点数家族包括float,double,long double类型。

float的范围定义在float.h中

char的范围:-128~+127

unsigned char的范围为:0~255

详细解读浮点数在内存中的存储规则

根据IEEE(电器和电子工程协会)754,任意一个二进制浮点数v可以表示成下面的形式:

  • (-1)^S *M *2^E
  • (-1)^S表示符号位,当S=0,V为正数,当S=1,V为负数
  • M表示有效数字,大于等于1,小于等于2
  • 2^E表示指数位

eg:

5.5在内存中的存储
首先将它写成二进制的格式
101.1
1.011*2^2
所以就为
(-1)^0*1.011*2^2
所以
S=0
M=1.011
E=2
IEEE754规定,对于32位浮点数,最高位1位是符号位s,接着的8位是指数E,最后的23位为有效数字M;对于64位浮点数,最高位1位是符号位S,接着11位是指数E,剩下的52位为有效数字M,存M只存小数点后面的位。针对E来说,E为一个无符号整数,这意味着如果E为8位,他的取值范围为0-255,如果E为11位,他的取值范围为0-2047,但是我们知道,科学计数法中的E是可以出现负数的,,所以IEEE754规定,存入内存时,E的真实值,必须加上一个中间数,对于8位的E中间数为127,对于11位的E,中间数为1023.
存储很容易,但是取出来,要分情况讨论:如下所示:
E不全为0或者不全为1:那么就用现有的值减去127,得到E原来真正的值;E全为0:这时,浮点数的指数E等于1-127或者(1-1023)即为真实值,有效数字M不再加上第一位的1,就变成0.xxxxx,因为这个数字实在是太小了;E全为1,这时,如果有效数字M全为0,表示正无穷大(正负取决于符号位s);

举个例子

5.5在内存中的存储
最高位为0
中间的8位加上127,结果为129 10000001
小数点后为011补齐为01100000000000000000000
所以5.501000000101100000000000000000000
浮点数的存储
#include<stdio.h>
int main()
{
	int n = 9;
	//9存在内存中,存储的时他的补码
	//00000000000000000000000000001001
	float* pFloat = (float*)& n;
	//要把一个整形,转换层成一个浮点型,那么现在就来转换一下
	//0 00000000 00000000000000000001001
	//又因为上述浮点型的存储中E全为0,所以这个数字是一个无限接近于0的数字 ,所以打印的结果为0.000000
	printf("n的值为:%d\n", n);
	//以整形的形式存入,再以整形的形式取出,那么结果当然还是整形了,所以说结果为9;
	printf("*pFloat的值为:%f\n", *pFloat);
	//要把一个整形,转换层成一个浮点型,那么现在就来转换一下
	//0 00000000 00000000000000000001001
	//又因为上述浮点型的存储中E全为0,所以这个数字是一个无限接近于0的数字 ,所以打印的结果为0.000000
	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	//以浮点数的形式存进去,以浮点数的形式往出拿
	//先将9写成二进制的形式
	//1001.0
	//1.001*2^3;
	//0 100000010 00100000000000000000000  --补码
	//整数,原,反,补相同
	//所以结果为010000001000100000000000000000000  --结果为1091567616
	printf("*pFloat的值为:%f\n", *pFloat);
	//以浮点数形式存进去,再以浮点是结果拿出来,结果是不会变化的
	//结果为9.000000
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值