• union 与struct的空间计算


    一、x86

    总体上遵循两个原则:

    • 整体空间----占用空间最大的成员(的类型)所占字节数的整数倍
    • 对齐原则----内存按结构成员的先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整倍数,如果不够则补齐,以此向后类推
    说明:假定结构体是从地址0开始依次存放各个变量的

    struct
    s1 { 变量占据内存位置 去掉余下变量后结构体所占内存空间 char a; //0 //1 double b; //8-15 //16 int c; //16-19     //24 char d; //20      //24 short e; //22-23      //24 short f; //24-25      //32 } student;

     详细解释:sizeof()用法汇总

    为什么会有这样的规定呢?

        这一定与处理器的字长有关(处理器一次存取数据的宽度)和编译器对结构体变量的处理有关。不幸的是,本人对x86架构不甚熟悉,只能借助与ARM结构来说明这个问题。

    二、ARM

    总体上遵循两个原则:

    • 整体空间----如果含有>4字节类型的成员,整体空间是4字节数的整数倍;反之,都是<=4字节的成员,占用空间最大的成员类型所占字节数的整数倍
    • 对齐原则----内存按结构成员的先后顺序排列。当排到该成员变量时,倘若该成员>4字节,其前面已摆放的空间大小必须是4的整倍数;倘若该成员<=4字节,其前面已摆放的空间大小必须是该成员类型大小的整倍数,如果不够则补齐。以此向后类推
    
    
    说明:假定结构体是从地址0开始依次存放各个变量的
    struct s1
      {          变量占据内存位置    去掉余下变量后结构体所占内存空间
      char a;       //0             //1
      double b;     //4-11          //12
      int c;        //12-15         //16
      char d;      //16            //20
      short e;     //18-19         //20
      short f;     //20-21         //24
     }student;
     

     为什么会有这样的规定呢?

        这一定与处理器的字长有关(处理器一次存取数据的宽度),所以必须先将ARM的字长,实际上涉及的内容是load/stort存储方式。ARM字长是32位,4个字节。也就是说,无论如何它都要使用32位数据总线(虽然它也支持字节/半字传送)。

    ldr指令

    什么意思呢?看程序你就知道了。

    int类型变量的存储

        AREA    Init,CODE,READONLY
        IMPORT main
    
        ENTRY
        
    _entry
        ldr r0,=0x12345678
        ldr r1,=0x1000
        str r0,[r1]
        
        ldr     r2,[r1]       ;r2=0x12345678
        ldr     r2,[r1,#1]    ;r2=0x78123456         不对齐发生旋转
        ldr     r2,[r1,#2]    ;r2=0x56781234
    
        bl main 
        END

        试想,倘若我们定义了一个int变量,值为0x12345678,按照小端格式在0x1000、0x1001、0x1002、0x1003,依次存放的数据是0x78、0x56、0x34、0x12,而我们再从这儿(0x1000)取的时候,还是0x12345678。

        假设我们按照小端格式存但是没有对齐(4字节对齐),在0x1001、0x1002、0x1003、0x1004,依次存放0x78、0x56、0x34、0x12,再假设0x1000单元存了一个0xab。那么我们再从这儿(0x1001)取的时候,取出来的就是0xab345678,显然读到的不是之前存的数据。

        就算是,有一个非常聪明的编译器,知道如果没有对齐存放的话,将来取的时候,要从0x1001、0x1002、0x1003取一部分(一条指令),然后再从0x1004取一部分(一条指令),最后整合(好几条指令),这样的工作实在是麻烦,编译器的效率是极低的。

        所以,最好的办法就是一开始存数据的时候,就根据其类型合适的对齐存放。例如int变量,就给它分配到能被4整除的地址上(实际上在它之前的存储空间大小就是4的倍数),而不要将其分配在不能被4整除的位置上。

        倘若是double类型的变量,实在无可奈何,存的时候只有分两次存,读的时候分两次读,这也是32位机最快的方法。倘若没有对齐,不知道要在存取时折腾多少次。也就是说,把double类型(其实还有其他>4字节的类型)都是放在以4为倍数的地址上。

    char型变量的存储

        至于char型变量,是没什么要求的。

       ldr r0,=0x12345678
        ldr r1,=0x1000
        str r0,[r1]
        
        ldrb    r2,[r1]       ;r2=0x00000078
        ldrb    r2,[r1,#1]    ;r2=0x00000056
        ldrb    r2,[r1,#2]    ;r2=0x00000034

        ldrb指令,你从哪个地方读,就返回你想要的值,不会发生什么移位旋转的问题。所以,你把一个char型变量,放在任意位置都行,ldrb指令都能准确无误的将其取出。

    short变量的存储

        还有short变量,这个也是有说唱的。情况也有些复杂,但没有ldr指令那么复杂。 

        ldr r0,=0x12345678
        ldr r1,=0x1000
        str r0,[r1]
        
        ldrh     r2,[r1]       ;r2=0x00005678
        ldrh     r2,[r1,#1]    ;r2=0x00005678             不对齐读的还是0x10000的内容
        ldrh     r2,[r1,#2]    ;r2=0x00001234

        试想,倘若我们定义了一个short变量,值为0x5678,按照小端格式在0x1000、0x1001,依次存放的数据是0x78、0x56,而我们再从这儿(0x1000)取的时候,还是0x00005678。

        假设我们按照小端格式存但是没有对齐(2字节对齐),在0x1001、0x1002 ,依次存放0x78、0x56,再假设0x1000单元存了一个0xab。那么我们再从这儿(0x1001)取的时候,取出来的就是0x000078ab,显然读到的不是之前存的数据。

        所以在存储short变量时,是存在以2为倍数的地址上。

    回到正题

        由上可知,我们知道了变量在存储过程中对地址的限制。通常,这些非常底层的东西,程序员是无需知道的。只不过,当用到struct结构体时,会把这个问题翻出来。

        结构体变量的成员是按次序在内存中排放,排放时候也需要遵从上边的限制。现在,那两条规则的原因就是这样了。

    结论

        不同架构的处理器对应着一定的编译器,这些不同的编译器对struct变量的处理是不一样的。

  • 相关阅读:
    Kattis
    HackerRank
    HackerRank
    牛客小白月赛1 C 分元宵【快速幂】
    牛客小白月赛1 J おみやげをまらいました 【MAP】
    logback与Spring、SpringMVC结合使用教程
    解决maven项目pom报错
    sz与rz命令
    cassandra 测试数据库
    ubuntu 无法解析主机的解决方法
  • 原文地址:https://www.cnblogs.com/amanlikethis/p/3444061.html
Copyright © 2020-2023  润新知