数据在内存中是怎么存储的?

在C语言中,为编程者提供了很多数据类型,不仅有内置的数据类型,还有自定义数据类型。内置数据类型有char、int、long、float、double等,自定义数据类型有结构等,那么这些内置数据类型的数据在内存中是怎么存储的呢?

这里只讲解内置数据类型,因为自定义数据类型的存储与内置数据类型的存储不同,那么C语言的内置数据类型有下面几种。

/*整数家族*/
char                 //字符型
unsigned char        //无符号字符型

short                //短整型
unsigned short       //无符号短整型

int                  //整型
unsigned int         //无符号整型

long                 //长整型
unsigned long        //无符号长整型

long long            //更长的整型
unsigned long long   //无符号更长的整型

/*浮点数家族*/
float                //单精度浮点数
double               //双精度浮点数
long double          //更长的双精度浮点数

在这里我们把内置的数据类型分成整数家族和浮点数家族,因为它们在内存中的存储并不相同。

一、整数家族在内存中的存储:

整数在内存中的存储比较简单,我们都知道整数在内存中的存储是以补码存储的,还不了解原码、反码、补码的可以参考我这篇文章:一次讲透原码、反码和补码_爱吃柠檬呀的博客-CSDN博客。那么我们在这里探讨一下为什么是以补码的形式存储,而不是原码或反码。

举例:-10 + 5 = -5(我们这里以8个低位来计算

-10的原码:10001010

-10的反码:11110101

-10的补码:11110110

5的补码:    00000101

相加之后的补码:11111011

将这个二进制序列转化为反码:10000100

转化为原码:10000101,我们发现这个二进制序列就是-5,这是按照在内存中以补码的形式存储计算的。

按照原码计算:-10 + 5 = -5

-10的原码:10001010

5的原码:   00000101

相加之后的原码:10001111,这个二进制序列是-15还是143我们无从得知,因为用原码存储的话,最高位到底是符号位还是数值位我们并不清楚,另外这两个结果也和计算的结果是-5不相符,所以整数在内存不可能以原码的形式存储。

按照反码计算:-10 + 5 = -5

-10的原码:10001010

-10的反码:11110101

5的反码:   00000101

相加之后的反码:11111010,这个二进制序列是-122还是250也同样不得知,所以整数在内存中的储存也不可能是反码。反码是原码与补码之间相互转换的中间产物,所以如果是以反码存储,那还要补码干什么?

综上,我们就知道了整数在内存中就是以补码形式存在的。

二、大小端介绍:

大小端是知道整数在内存中是以补码的形式存储的之后,探讨在补码在内存到底是以怎么的方式存储的,这就涉及到了大小端

大端就是把数据的低位字节的数据存放到高地址处,高位字节的数据存放到低地址处。

小端就是把数据的低位字节的数据存放到低地址处,高位字节的数据存放到高地址处。

 那么为什么会有大端存储和小端存储呢?实际上不仅要把数据放到内存中方便,还要方便取出来,所以在存储时就有了这两种储存模式,在VS编译器中,数据就是以小端的形式存储的。

三、浮点数家族在内存中的存储:

浮点数在内存中的存储就与整数完全不一样了,整数只是简单的使用补码存储,而浮点数比较麻烦。

浮点数在内存中并不会精确保存,这也很好理解,现实中也有一些十进制浮点数是不能精确表示的,比如循环小数等,所以将一个浮点数转换为二进制浮点数时,不能精确存储也就很好理解了。

float和double也不能存的下所有的小数,所以有一些浮点数在内存中不能精确保存。

浮点数的存储要以V = (-1) ^ S * M * 2 ^ E这种形式存储,其中(-1) ^ S表示符号位,当S = 0时,V为正数。当S = 1时,V为负数,M表示有效数字,范围是1,2 ^ E表示指数位。比如十进制5.5在内存中的存储结果为(-1)^0*1.011*2^2。首先十进制5.5转换为二进制是101.1,然后把小数点向左移动两位就变成1.011*2^2(因为是二进制,所以指数形式的底数是2),最后加上符号位即可。

下面举个例子:

//浮点数在内存中的存储
#include<stdio.h>
int main()
{
    float f = 5.5;
    //5.5的二进制表示为101.1
    //对应的形式就是(-1)^0 * 1.011 * 2^2
    //E的值是2,加上127是129,对应的二进制是10000001
    //转换成二进制:0 10000001 01100000000000000000000
    //转换二进制时,没有用到的位用0补齐
    //将二进制序列转换成十六进制验证:40B00000

    printf("%x\n", *(int*)&f);//打印结果为:40b00000
    return 0;
}

我们知道了浮点数在内存中的存储,那么该怎么从内存中取出来呢?这就分为以下是三种情况:

当E不为全0或不为全1时,读取E时需要加上-127(32位)或加上-1023(64位),有效数字M加上第一位的1,这也是最常见的一种情况,怎么存储的就怎么读取出来。

当E为全0时,浮点数的指数E = 1 - 127(32位)或E = 1 - 1023(64位)即为真实值。有效数字M不加第一位的1,直接还原成0.xxxxxxx的小数,这样做是为了表示±0,一个非常接近0的数字。

因为当E为全0时,那么原来的值应该是-127(因为存储需要加上127),此时的E是一个非常非常小的数字,接近0的数字。这个数字相当于1/2^127,这个数字是多小已经不重要了,因为计算机已经把它当成0计算了。当E = 1 - 127(32位)时,E的值是-126,表示的数是1/2^126,虽然没有1/2^127小,但是也是非常接近0的数字了。

当E为全1时,浮点数是一个非常大的数字,32位时有2^128,64位有2^1023,已经接近无穷大了。

说白了,如果E不全0或全1时,怎么存的就怎么取,如果全0,编译器就会默认当成0(这是一个非常非常小的数字),如果全1,编译器就默认当成无穷大(这也是一个非常非常大的数字)。

四、补充知识:

这里补充一下数据类型的取值范围:

(具体可以参考我的另一篇文章:C语言中的数据类型_爱吃柠檬呀的博客-CSDN博客

数据类型字节数取值范围
char(有符号字符型)1-128 ~ 127(2^7~2^7-1
unsigned char(无符号字符型)10 ~ 255(0~2^8-1)
short(有符号短整型)2-32768 ~ 32767(-2^15~2^15-1
unsigned short(无符号短整型)20 ~ 65535(​0~2^16-1)
int(有符号整型)4-2147483648 ~ 2147483647(-2^31~2^31-1)
unsigned int(无符号整型)40 ~ 4294967295(0~2^32-1)
long(有符号长整型)4-2147483648 ~ 2147483647(-2^31~2^31-1)
unsigned long(无符号长整型)40 ~ 4294967295(0~2^32-1)
long long(有符号更长的整型)8-9223372036854775808 ~ 9223372036854775807(-2^63~2^63-1)
unsigned long long(无符号更长的整型)80 ~ 18446744073709551615(0~2^64-1)
float(单精度浮点型)41.17549×10^(-38)~3.40282×10^(38)(绝对值)
double(双精度浮点型)82.22507×10^(-308)~1.79769×10^(308)(绝对值)
long double(长双精度浮点型)122.22507×10^(-308)~1.79769×10^(308)(绝对值)

以上就是我这次分享的内容啦!写的不好,请多担待!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值