• 深度剖析数据在内存中的存储


    小编认为要想成为一个好的程序员,不能仅仅只做到会使用,而要做到理解其本质 。做到可持续发展。接下来小编会向大家介绍数据在内存中究竟是如何存储与运算的,也算是修炼内功了吧。

    目录

    一、数据类型介绍

    1.整型家族

    2.浮点数家族

    3.构造类型

    4.指针类型

    5.空类型

    二、整型家族

    1.原、反、补码

    2.大小端字节序

    2.1方法一(指针访问)

    2.2方法二(联合体)

    3.整型提升

    整型提升的意义:

    整型提升的规则:

    4.有无符号的数据区分

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

    IEEE 754规定

    M

    E


    一、数据类型介绍

    1.整型家族

    char:unsigned char、signed char

    short:unsigned short、signed short

    int:unsigned int、signed int

    long:unsigned long、signed long

    值得注意的是,char类型也属于整型家族原因是:字符本质为ASCII值,是整型,所以划分到整型家族。另外char究竟是singed char还是unsigned char取决于编译器的底层实现。

    2.浮点数家族

    float、double、long double

    3.构造类型

    数组类型

    结构体类型 struct

    枚举类型 enum

    联合类型 union

    4.指针类型

    int * 、char *、float*、void* ……

    5.空类型

    void 表示空类型(无类型)
    通常应用于函数的返回类型、函数的参数、指针类型等

    二、整型家族

    1.原、反、补码

    计算机中的整数有三种2进制表示方法,即原码、反码和补码。

    三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”。

    而数值位正数的原、反、补码都相同。负整数的三种表示方法各不相同。

    原码:直接将整型数字按照正负数形式翻译成二进制,即可得到原码

    反码:将原码的符号位不变,其他位依次按位取反即可得到反码

    补码:将反码加一即可得到补码

    在计算机系统中,整型数值一律使用补码来表示与存储,但是为什么呢?

    1.使用补码可以将符号位和数字域统一处理。

    2.加法和减法可以统一处理(CPU只有加法器)。

       例:CPU计算1 - 1时,会计算1 + (-1)

    3.补码与原码相互转换,其运算过程相同,不需额外的硬件电路。

    2.大小端字节序

    接下来,看看下面这张图:

    变量a的补码用十六进制表示不是0x00000001吗,在内存中看怎么会是这个样子呢?这就不得不提到大、小端字节序存储模式了。

    大端(存储)模式:

    是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

    小端(存储)模式:

    是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。

    :一个 16bit的short型变量x ,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节, 0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即 0x0011中。小端模式,刚好相反。

    从上图可以看出小编所用的环境是小端字节序存储模式,那么为什么会有大小端之分呢?

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

    我们常用的X86(32位)结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。


     

     百度就曾有一年的笔试题,考过这个知识点。这道题,我将讲述两种解法:

    2.1方法一(指针访问)

    1. #include
    2. int check_sys_1()
    3. {
    4. int i = 1;
    5. char* p = (char*)&i;
    6. return *p;
    7. }
    8. //简化后:
    9. int check_sys()
    10. {
    11. int i = 1;
    12. return (*(char*)&i);
    13. }
    14. int main()
    15. {
    16. int ret = check_sys();
    17. if (ret == 1)
    18. {
    19. printf("小端\n");
    20. }
    21. else
    22. {
    23. printf("大端\n");
    24. }
    25. return 0;
    26. }

    2.2方法二(联合体)

    1. #include
    2. int check_sys()
    3. {
    4. union
    5. {
    6. int i;
    7. char c;
    8. }un;
    9. un.i = 1;
    10. return un.c;
    11. }
    12. int main()
    13. {
    14. int ret = check_sys();
    15. if (ret == 1)
    16. {
    17. printf("小端\n");
    18. }
    19. else
    20. {
    21. printf("大端\n");
    22. }
    23. return 0;
    24. }

    联合体的所有成员在内存中具有相同的首地址,共占同一段内存空间,这些成员可以相互覆盖。

    利用该条特性实现大端与小端的判断,该方法实在"秀"。

    3.整型提升

    C的整型算术运算总是至少以缺省整型(即默认整型)类型的精度来进行的。
    为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

    整型提升的意义:

    表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
    一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
    因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
    度。
    通用CPU (general-purpose CPU) 是难以直接实现两个字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
     

    整型提升的规则:

    整型提升是按照变量的数据类型的符号位来提升的,若无符号直接补0。

    4.有无符号的数据区分

    以char类型为例:

    字符型数据char可分为unsigned char(无符号)和signed char(有符号)

    具体char代表的是有符号字符型还是不符号字符型,取决于你编译器的具体实现。以VS2019为例,char所代表的便是signed char。

     

     由此可见,signed char的取值范围是-128到127,unsigned char的范围是255.

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

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

    (-1)^S * M * 2^E(即用科学计数法表示)

    IEEE 754规定

    (1)对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。

    (2)对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

    M

    计算机内部保存M时,默认这个数的第一位总是1。因此,可以被舍去,只保存后面的小数部分,读取时再将第一位的1加上。节省一位有效数字,可提高小数部分的精确度。

    E

    (1)放入

    E为一个无符号整数。若E为8位,它的取值范围位0-255。若E为11位,它的取值范围为0-2047。

    但科学计数法中的E是可以出现负数的。所以IEEE 754规定,存入内存时E的真实值必须加上一个中间值。对于8位的E,中间值位127。对于11位的E,中间值为1023。

    (2)取出

    1.E不全为0或不全为1

            E减127(1023)得E的真实值——即正常反向计算

    2.E全为0

            浮点数的指数E等于1-127(或者1-1023)即为真实值。有效数字M不再加上第一位的1,而是还原为0.xxx的小数。这样是为了表示+/-0,以及接近于0的很小的数字。

  • 相关阅读:
    Qt鼠标跟踪
    mac: docker安装及其Command not found: docker
    使用 Docker 部署 TailChat 开源即时通讯平台
    Echarts图表 多表联动及图表数据还原
    GOODIX TOUCH驱动移植
    机器学习:集成学习(Python)
    ARMday06(总线、串口、RCC章节分析)
    docker版本的Jenkins安装与更新技巧
    代理IP和Socks5代理:跨界电商与全球爬虫的关键技术
    ros2知识:在单个进程中布置多个节点
  • 原文地址:https://blog.csdn.net/GG_Bruse/article/details/126251667