数据在内存中的存储

本文详细介绍了整数在内存中的补码存储方式,区分了原码、反码和补码,并讨论了大小端字节序的概念。还探讨了浮点数的IEEE754标准存储结构,以及如何通过编程判断机器的字节序。最后,涉及了死循环的产生原因,涉及到整数溢出和浮点数的取值规则。
摘要由CSDN通过智能技术生成

数据在内存中的存储

整数在内存中的存储

整数在内存中是以补码的形式存储的

整数的二进制表示有三种:原码、反码、补码

对于有符号整数,它的最高位视为符号位,1表示负,0表示正。

正整数的原码、反码、补码相同。

负整数的原码、反码、补码不同:

原码:将原数直接写成二进制表示的形式

反码:符号位不变,其余各位取反

补码:反码加一

补码转化为原码:1.补码建1,再取反(符号位不变)2.补码取反(符号位不变),再加1,与原码转化为补码的过程一致。

为什么计算机存储的是补码呢?

在计算机系统中,数值⼀律⽤补码来表⽰和存储。

原因在于,使⽤补码,可以将符号位和数值域统⼀处理; 同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。


大小端字节序存储

大小端

超过一个字节的数据在内存中存储时会出现存储顺序的问题,基于此,我们按照不同的存储顺序分为大端字节序存储和小端字节序存储。

大端字节序存储

是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。

小端字节序存储

是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。

  • 以VS为例:

首先,VS调试的内存窗口是以十六进制表示的,这么做只是为了方便展示,并不能说明内存中是如此存储的。

129的二进制表示为:00000000 00000000 00000000 10000001

将上面的二进制序列化为十六进制表示:00 00 00 81

对于这个十六进制序列,81相对00就是低位,我们联想一下十进制的54,4是个位,就是低位。

在这里插入图片描述

在这里插入图片描述

而我们观察第二张图,低位的81存储在了较低地址处,按照上面介绍的大小端字节序存储,我们可以得出结论VS采用小端字节序存储。


了解到这里,我们尝试应对百度的一道题目:

写一个程序,判断当前机器是大端还是小端:

//1.指针
int check_sys()
{
    int i = 1;
    return *((char*)&i);
}

int main()
{
    int ret = check_sys();
    if (1 == ret)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}
//思路就是,我们创建一个整型类型,赋值为1,1的二进制表示为00000000 00000000 00000000 00000001
//取变量的地址,将它强制类型转换为char*类型,然后解引用,char*类型解引用访问一个字节。
//如果结果是0,证明当前机器存储1的顺序是00000000 00000000 00000000 00000001,为大端字节序存储。
//如果结果是1,证明当前机器存储1的顺序是00000001 00000000 00000000 00000000,为小端字节序存储。
//联合
int chack_sys()
{
    union
    {
        int i;
        char c;
    }un;
    un.i = 1;
    return un.c;
}

int main()
{
    int ret = check_sys();
    if (1 == ret)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

在这里插入图片描述

我们对i赋值1,会改变c的值

如果是小端字节序存储:c的位置存储的是00000001

如果是大端字节序存储:c的位置存储的是00000000

如此我们便判断出当前机器的大小端。


我们知道,char类型其实也是整型家族的一员,char也有无符号char和有符号char,char究竟默认为有符号char还是无符号char,这是不确定的,取决于编译器。不过,VS的char默认就是有符号char

这段代码的输出结果是什么?

#include <stdio.h>

int main()
{
    char a = -1;//标号1
    signed char = -1;//标号2
    unsigned char = -1;//标号3
    printf("a=%d,b=%d,c=%d",a,b,c);
    return 0;
}

VS2022环境下,输出的结果是

在这里插入图片描述

为什么会出现这种情况?

观察题目,标号1和标号2的语句其实是一样的,因为VS2022下的char默认为有符号char。

首先写出-1在内存中存储的补码:11111111 11111111 11111111 11111111

a变量无法存储32个bit位,发生截断,截取最低位起8个bit位:11111111,这就是a变量在内存中存储的二进制序列

我们以%d(有符号整数)的形式打印,首先会整型提升,整型提升有两种情况,char是有符号的,所以整型提升时补符号位,这里是1,得到:11111111 11111111 11111111 11111111

以有符号整型打印,计算机认为整型提升后的二进制序列是一个有符号整数的补码,最高位是1,为负数,原码、反码、补码不同,打印时需要转化为原码:10000000 00000000 00000000 00000001,转化过来,就是-1,所以打印a、b的结果都是-1。

对于c我们采取同样的步骤分析:

-1的补码为:11111111 11111111 11111111 11111111,存储在unsigned char中会发生截断,存储的是:11111111

以%d形式打印,发生整型提升,此时c是无符号char类型,整型提升时补0,得到:00000000 00000000 00000000 11111111

最高位为0,是正数,原码、反码、补码相同,所以整型提升后的二进制序列就是原码,就是255,所以打印255。


我们再看一道题目:

#include <stdio.h>

unsigned char i = 0;
int main()
{
    for(i = 0; i <= 255; i++)
    {
        printf("hello world\n");
    }
    return 0;
}

打印结果是什么?

答案是:死循环,一直打印hello world。

unsigned char类型的取值范围是0~255

当i = 255时,其在内存中的存储是11111111

当第256次循环时,i 理应是256,但是不是这样的:

256的二进制表示为:00000000 00000000 00000001 00000000

存储在i中,会发生截断只取8个bit位00000000,i的值其实是0,然后再次到255,如此构成死循环,也就是说i <= 255这个条件恒成立。

浮点数在内存中的存储

浮点数在内存中的存储不同于整数在内存中的存储。

根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

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

  • **(-1)^S: ** 为符号位,S为0表示正数;S为1表示负数。
  • **M: ** 表示有效数字,它的大小大于1,小于2.
  • 2^E: 表示指数位

对于上述知识,我们举个例子来解释:

十进制表示的浮点数5.5
二进制表示:101.1 (小数点后面的第一位权重为2^-1)
我们将它写成由S、M、E表示的形式:(-1)^0 * 1.011 * 2^2
S;0
M:1.011
E:2

我们发现,如果我们知道任意一个浮点数的上述表示形式的S、M、E,我们就能将这个浮点数还原成我们常见的十进制表示形式。

其实,浮点数在内存中存储的就是S、M、E的值。


对于32位的浮点数(float),它的最高位存储S,8个bit位存储E,23个bit位存储M

在这里插入图片描述
对于64位的浮点数(double),它的最高位存储S,11个bit位存储E,52个bit位存储M
在这里插入图片描述

首先,最高位存的S很简单,是负数就存1,是正数就存0。

我们前面举了十进制5.5的例子,M为1.011,存储M时其实并不会存小数点前的1,23个位存储的是小数点后面的数011,这样做的好处是:能够多存储一位,增加了浮点数存储的精确度。

标准规定E是一个无符号整数

我们清楚,科学计数法的指数可能为负数。针对这种情况,引入了一个中间值(32位浮点数为127,64位浮点数为1023),规定在存储E时,要先加上这个中间值,比如某个32位浮点数的E为3,那么真实存储的为127 + 3 = 130,如果E为-1,那么真实存储的是-1 + 127 = 126。标准规定的这个中间值是很巧妙的,保证不会出现某个指数E加上中间值为负数的情况,这一点我们不必担心。

S的取很简单,我们不多赘述。

我们在取的时候大致分为三种情况:

  • E不全为0且E不全为1

    将存储的E拿出来后,减去中间值,得到E的值,然后拿出M的值,并在前面加上1.

  • E全为0

    我们想,我们存储E时要加上一个中间值,而加上了这个中间值,E在计算机种存储的仍然全为0,所以我们知道,这会是一个非常小的数。对于这个非常小的数,取的时候E默认为1 - 127,同时取出M时,不再会加上1,而是以0点几的形式拿出来。

  • E全为1

    这种情况一定是一个很巨大的数,这个数取出来后是无穷大,符号由S决定。

我们举个例子:

例如我们存十进制的5.5
二进制表示:101.1
转化为S、M、E的形式:(-1)^S * 1.011 * 2^2
我们假设这个数是float类型的
那么它存储在计算机的二进制序列为:
0 10000010 01100000000000000000000

在这里插入图片描述

解析

存:

正数,所以S是0

E是2,32位浮点数存储E时会加上中间值127,所以存储在计算机内部的其实是127 + 2 = 129这个数字

M存储时,不存小数点前面的1,只存储小数点后面的011,不够的补0,所以M存储为01100000000000000000000

取:

S取出来是0,所以是正数

E取出来是129,减去中间值127 ,得到2

由于E的存储不全为0且不全为1,所以M取出来后加上1,得到1.011

再根据取出来的S、M、E就能还原出十进制32位浮点数5.5。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值