C语言之数据的存储总结

目录

前言

一、整型在内存中的存储

 二、浮点型在内存中的存储


前言

之前我们粗略地总结过数据的类型,现在我们来总结一下数据的存储


一、整型在内存中的存储

1.计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位数值位两部分,符号位都是用0表示,用1表示负”,而数值位正整数原、反、补码都相同负整数的三种表示方法各不相同,原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码,如图:是-1的原反补码:

 

2.对于整型来说:数据存放内存中其实存放的是补码:

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

 变量a的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的。此处a已经被分配了4个字节的空间。我们可以看到a存储的是补码,注意:这里是16进制,1个16进制数=4个2进制数,但是为什么是10000000?这个顺序感觉怪怪的。其实这涉及到大小端存储的问题。

3.大小端介绍

(1)大端(存储)模式:指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。(大端字节序存储)

        小端(存储)模式:指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。(小端字节序存储)

如图所示:(VS的 X86 结构是小端模式)

(2)存在大小端的原因:

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的 short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式

(3)我们来看一道题:

设计一个小程序来判断当前机器的字节序 
#include <stdio.h>
int check_sys()
{
    int i = 1;

    return (*(char *)&i);
}
int main()
{
    int ret = check_sys();

    if(ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }

    return 0; 
}

解析如下:

我们都知道指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。(char *)&i所以我们将i的地址(类型为int*)强制类型转换为(char*),再解引用,访问的就是i的第一个字节,如果访问到是1,那么就是小端字节序存储;如果访问到是0,那么就是大端字节序存储

4.接下来,我们来看以下一些练习:

(1)

#include <stdio.h>
int main()
{
    char a= -1;//-1的补码存到a里面去,然后发生整型截断
    signed char b=-1;//-1的补码存到b里面去,然后发生整型截断
    unsigned char c=-1;//-1的补码存到c里面去,然后发生整型截断

    printf("%d %d %d",a,b,c);//-1 -1 255

    return 0; 
}

解析如下左图:

 (2)

#include <stdio.h>
int main()
{
    char a = -128;

    printf("%u\n",a);//4294967168
    //以无符号整型的形式打印,此时就不存在原反补码的概念了

    return 0; 
}

解析如上右图:

(3)

#include <stdio.h>
int main()
{
    char a = 128;//char存的有效值:-128~127

    printf("%u\n",a);//4294967168

    return 0; 
}

解析如下左图: 

 

(4)

#include <stdio.h>
int main()
{
    int i = -20;
    unsigned  int  j = 10;

    printf("%d\n", i + j);//10
    //按照补码的形式进行运算,最后格式化成为有符号整数

    return 0;
}

解析如上右图:

(5)

#include <stdio.h>
int main()
{
    unsigned int i;//无符号数,所有位都是有效位
    //-1补码:11111111111111111111111111111111即十进制的4294967295

    for(i = 9; i >= 0; i--) 
    {
        printf("%u\n",i);//9 8 7 6 5 4 3 2 1 0 4294967295 4294967294.......死循环
    }

    return 0;
}

(6)

#include <stdio.h>
#include <string.h>
int main()
{
    char a[1000];
    int i;

    for(i = 0; i < 1000; i++)
    {
        a[i] = -1-i;
    }
    printf("%d",strlen(a));//255

    return 0; 
}

解析如下:

 (7)

#include <stdio.h>
int main()
{
    unsigned char;

    for(i = 0;i <= 255;i++)
    {
        printf("hello world\n");//此处会打印无限个hello world
    }

    return 0;
}

解析如下:

 二、浮点型在内存中的存储

1.浮点型存储规则:

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式: (-1)^S * M * 2^E

①(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数②M表示有效数字,大于等于1,小于2③2^E表示指数位。
如: 十进制的 5.0 ,写成二进制是 101.0 ,相当于 1.01×2^2 那么 S=0 M=1.01 E=2
       十进制的 -5.0 ,写成二进制是 - 101.0 ,相当于 - 1.01×2^2 。那么 S=1 M=1.01 E=2

IEEE 754规定: 对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M;对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。,如图所示:

IEEE 754有效数字M和指数E,还有一些特别规定:

前面说过, 1≤M<2 ,也就是说, M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。IEEE 754规定,在 计算机内部保存M时,默认这个数的一位总是1 ,因此可以被舍去,只保存后面的xxxxxx部分。比如保存 1.01 的时候,只保存 01 ,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是 节省1位有效数字 。以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存24 位有效数字。
至于指数 E:
E为一个无符号整数: 这意味着,如果E 8 位,它的取值范围为 0~ 255 ;如果 E 11 位,它的取值范围为 0~ 2047 。但是,我们知道,科学计数法中的 E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。 比如, 2^10 E 10 ,所以保存成32 位浮点数时,必须保存成 10+127=137 ,即 10001001 。举一个5.5存储的例子,如图:

 

 然后,指数E从内存中取出还可以再分成三种情况:

E 不全为 0 或不全为 1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1 ,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000
②E 全为 0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
③E 全为 1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)
我们来看一道题:
int main()
{
    int n = 9;
    //9在内存中的存储:00000000000000000000000000001001(原反补码相同)
    float *pFloat = (float *)&n;
    //pFloat认为9是浮点数 所以9在内存中的存储应该是以浮点型的方式存储0 00000000 00000000000000000001001
    //E全为0,∴E=1-127=-126
    //M=0.00000000000000000001001
    //0.00000000000000000001001*2^-126这个数基本趋于0,且%f只能打印小数点的后6位,因此0.000000

    printf("%d\n",n);//9
    printf("%f\n",*pFloat);//0.000000
    *pFloat = 9.0;
    //9.0的二进制:1001.0
    //科学计数法:(-1)^0*1.001*2^3
    //S=0
    //E=3   +127存储
    //M=1.001
    //0 10000010 00100000000000000000000
    //%d认为此时内存中存的是有符号整数,符号位为0,为正数,原反补相同,01000001000100000000000000000000转化为十进制:1091567616
    printf("%d\n",n);//1091567616
    printf("%f\n",*pFloat);//9.000000

    return 0; 
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值