【C语言基础】数据在内存中的存储

一、数据类型

1.1 数据类型基本介绍

C语言中数据类型主要包括内置类型自定义类型(构造类型)
内置类型:C语言本身就有的类型
自定义类型:自己可以创造或改变这个类型
大致分类如下:
​​​​在这里插入图片描述

2.2 数据类型的意义

1)数据类型决定了开辟内存空间的大小

使用int类型,就开辟4个字节,使用char类型,就开辟1个字节

例如:int – 4Byte , double – 8Byte

2)类型不同导致存储方式不同

例如:整型数据以补码方式存储,而浮点型数据的存储是按照IEEE754标准,存储了S、M、E相关的值

二、整数在内存中的存储

2.1 有符号与无符号

整数分为有符号整数和无符号整数,要弄清楚有符号与无符号,首先要了解符号位这个概念

符号位:二进制的最高位(最左边一位)是符号位

例如:
在这里插入图片描述

有符号数:用signed修饰,符号位为0表示正数,符号位为1表示负数

例如:
//举例
无符号数:用unsigned修饰,没有符号位的概念,只能表示0和正整数

注意:

1)只有整型和字符型才能被signed,unsigned修饰

2)整型默认是有符号的

3)char 是有符号还是无符号,取决于编译器,大部分是有符号的

打印有符号的整数:%d

打印无符号的整数:%u

signed认为最高位是符号位

unsigned认为最高位不是符号位

但都向内存申请相同大小的空间,类型只是用来创建变量

2.2 原码、反码、补码

当我们把一个数转换成2进制表示的时候,整数的二进制就有三种表示形式:
原码、反码、补码

无符号数:原码、反码、补码相同

有符号数

(1)正数:原码、反码、补码相同

(2)负数:

原码 = 数值按正负数的形式直接翻译成二进制形式

反码 = 原码符号位不变,其它位按位取反

补码 = 反码+1

补码转原码:取反+1

例如:

	int a = 10;
	//00000000 00000000 00000000 00001010 - 原码
	//00000000 00000000 00000000 00001010 - 反码
	//00000000 00000000 00000000 00001010 - 补码

	int b = -10;
	//10000000 00000000 00000000 00001010 - 原码
	//11111111 11111111 11111111 11110101 - 反码
	//11111111 11111111 11111111 11110110 - 补码

注意:

1)整数在内存中存储的是二进制序列的补码

2)在计算机运算的时候,都是以补码的方式来运算的

3)看运算结果,要看原码

使用补码的好处:

1.统一了正数和负数

2.加法和减法可以统一处理(CPU只有加法计算器)
例如 1减1的问题可以转换成1加上负1,即1 - 1 --> 1 + (-1)

三、大小端字节序和字节序的判断

3.1 字节序存储基本介绍

在计算机中,内存被分为一个个字节存储在内存中,对于超过一个字节的数据(比如 short-2 , int-4),
就需要考虑它在内存中存放顺序的问题。

字节序存储的两种常见方式为大端字节序存储和小端字节序存储

3.2 大小端字节序存储存储

在了解大小端字节序前,我们需要先了解高位、低位、高地址和低地址的概念

对数据:左边为高位,右边为低位;
对内存空间:左边为低地址,右边为高地址

例如:
在这里插入图片描述

  • 大端字节序存储
    将一个数据的低位字节序内容放到高地址,高位字节序内容放到低地址处

  • 小端字节序存储
    将一个数据的低位字节序内容放到内存的低地址处,高位字节序内容放到内存的高地址处

例如:
在这里插入图片描述

  • 如何判断当前机器是大端还是小端?

x86结构一般小端模式,KEILC51则为大端模式。很多的ARM,DSP都是小端模式,有些ARM处理器还可以由硬件来选择是大端模式还是小端模式

  • 不管是大端存储还是小端存储,最终都要将数据还原回来(怎么存的,怎么还原)

练习:写一个函数,判断当前机器的字节序

思路:

(1)定义一个变量a,值为1
1对应的二进制序列为 0x00 00 00 01
如果是大端存储那么内存中是00 00 00 01,如果是小端存储那么内存中是01 00 00 00
(2)拿到第一个字节
如果值是1那么就是小端存储;如果值是0,那么就是大端存储
具体步骤:拿到a的地址(int * 类型),将a强制类型转换成 char * ,然后再解引用

int check_sys()
{
       int a = 1;
       return *(char*)&a;//返回1是小端,返回0是大端
}
int main()
{
       
       if (check_sys())//int*
       {
              printf("小端\n");
       }
       else
       {
              printf("大端\n");
       }
       return 0;
}

四、浮点数在内存中的存储

4.1 浮点数基本介绍

浮点数的表示
(1) 一般写法,比如:3.1415926
(2)科学计数法,比如:1E10 表示1.0乘以10的10次方

浮点型包含:flaot,double,long double

  • 编译器默认一个小数是double类型,要想表示float类型,需要在数字后面加上 f 或F

  • 默认打印小数点后六位

  • 浮点数在内存不能保证精确保存

如果要比较两浮点数是否相等
1.确定可接受的误差范围
2.两者作差,取绝对值
例如:比较浮点数 f 和3.45是否相等就可以让 f 和 3.45 作差,如果它们之间的差值小于自己可接受的范围
(比如 0.00000001),那么就认为这两个浮点数相等

if(fabs(f-3.45)<0.00000001)//fabs求浮点数的绝对值,abs是求整数的绝对值
{
//相等
}else
{
//不相等
}

4.2 浮点数在内存中的存储

基本介绍
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式

V = (-1)^ S * M * 2 ^ E

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

例如:
10进制:5.5
对应的2进制:101.1
因为M要满足大于1小于2,所以化为 1.011* 2^2

5.5 = (-1)^0 * 1.011 * 2^2

S = 0,表示正数
M = 1.011,满足大于等于1小于2
E = 2

小数点前的权重:2^0 2^1 2^2 ……
小数点后的权重:2^(-1) 2^(-2) 2^(-3)……

存储规则

  • 浮点数的存储,存储的就是S,M,E 相关的值
  • 单精度的浮点数(float-32比特位):1bit存S,8bit存E,23bit存M
  • 双精度的浮点数(double -64比特位):1个bit存S,11个bit存E,52个bit存M

具体存储的过程
对于M:
M可以写成1.xxxxx的形式,xxxxx表示小数部分,由于计算机内部保存M时,默认第一位总是1,
所以IEEE 745规定计算机在存储的时候只存小数,输出的时候在前面补上1,这样可以节省1位有效数字。

比如32位的浮点数,留给M只有23位,如果将第一位舍去,那么就可以多保存一位小数,相当于保存了24位有效数字

对于E:
E是一个无符号整数,当E有8位时,取值范围为0 ~ 255;当E有11位时,取值范围为0 ~ 2047 ,但E有可能为负数

E为负数的情况:
10进制:0.5
对应的2进制:0.1 --> 1.0 * 2^(-1)

所以IEEE 745规定,存入E要加上一个中间值,如果E有8位,就加上127,如果E有11位,就加上1023,
拿出来时再减去中间值

浮点数取的过程

一般情况(E不为全0或全1)
对于E:E减去中间值
对于M:在小数点前面补上1

例如:
0.5的二进制为0.1 ,因为M的取值需要满足大于1小于2,所以将0.1转换为1.0 * 2 ^ (-1) ,再让 E+中间值
即(-1)+ 127 = 126,表示二进制的 0111 1110,再将M = 1.0 小数点前的1去掉,用0补齐23为
得到二进制表示形式:
在这里插入图片描述

特殊情况
(1)E为全0
E为全0时,规定E的真实值为1-1271-1023,此时M不再加上小数点前的1,而是还原为0.xxxx的小数

可以这么理解:
如果E有8位,那么E为全0也就是 E的真实值 + 127后结果为0 的情况,那么E的真实值就是-127

±1.xxxx * 2 ^ (-127) 2 ^ (-127) 表示2分之127次方,所以 ±1.xxxx * 2 ^ (-127)表示的就是正负无穷接近0的数字

(2)E为全1
二进制的11111111表示255,如果E有8位,那么E为全1也就是E+中间值=255,如果中间值是-127,那么E=128
浮点数表示为 ±1.xxxx * 2 ^ (128)
2 ^ (128) 表示2的128次方,所以 ±1.xxxx * 2 ^ (128)表示的就是正负无穷大的数字

练习1:

float f = 5.5f;

分析:

5.5 = 1.011 * 2 ^2

S = 0 ,正数
M = 1.011

E = 2,存的是 E+127 = 129 ,129对应的二进制序列为1000 0001

得到二进制表示形式 0 1000 0001 01100000000000000000000

让四个二进制位为1组 0100 0000 1011 0000 0000 0000 0000 0000

转换为16进制 0x40 b0 00 00

在这里插入图片描述

练习2:

int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);

*pFloat = 9.0;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);

分析:
(1)
9对应的二进制序列为 00000000 00000000 00000000 00001001
将n的地址强制类型转换成float*并赋给pFloat

以整数方式存储,以整数方式(%d) 打印,n的值为9

*pFloat 认为内存中存的是浮点数,所以 以浮点数的形式取出一个值,然后使用%f 打印
在这里插入图片描述
E为全0,表示的是正负无穷接近0的数字,所以打印结果为0.000000

(2)
pFloat = 9.0
9.0是浮点数,要转换为S,E,M的相关值才能存进去

9.0 = 1.001 * 2 ^ 3
9.0表示成 (-1)^0 * 1.001 * 2^3
S=0
M=1.001,存001,同时要补0凑够23位
E=3,存到内存要加127,得130,对应二进制:10000010
存到内存中的二进制序列为: 0 10000010 00100000000000000000000

以整数形式打印(%d),认为存的是补码,因为第一位为0,是正数,原、反、补码相同
n的值为1091567616

以浮点数方式存储,以浮点数方式打印,*pFloat的值为9.000000

运行结果:
在这里插入图片描述

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值