• [转] 结构体对齐


    C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。结构体到底怎样对齐?下面总结了对齐原则,在没有#pragma pack宏的情况下:

    原则1、普通数据成员对齐规则:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

    原则2、结构体成员对齐规则:如果一个结构里有某些结构体成员,则该结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

    原则3、结构体大小对齐规则:结构体大小也就是sizeof的结果,必须是其内部成员中最大的对齐参数的整数倍,不足的要补齐。

    补充一点,如果数组作为结构体成员,比如:char a[3]。它的对齐方式和分别写3个char是一样的,也就是说它还是按1个字节对齐。如果写: typedef char Array3[3];Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度3对齐。如果共用体作为结构体成员,则该共用体成员要从其内部最大元素大小的整数倍地址开始存储。

    还有一种对齐原则,和上面三条一致,可以参考着理解。

    成员对齐有一个重要的条件,即每个成员按自己的方式对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

    这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

    例1:struct {
                  short a1;
                  short a2;
                  short a3;
           }A;

           struct{
                  long a1;
                  short a2;
          }B;

    sizeof(A) = 6; 这个很好理解,三个short都为2。

    sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。

    例2:struct A{
                   int a;
                   char b;
                   short c;
            };

            struct B{
                   char b;
                   int a;
                   short c;
           };

    sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。

    sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

    深究一下,为什么是这样,我们可以看看内存里的布局情况。

                        a        b   c
    A的内存布局:1111, 1*,11

                        b       a       c
    B的内存布局:1***,1111,11**

    其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

    再看一个结构中含有结构成员的例子:

    例3:struct A{
                   int a;
                   double b;
                   float c;
           };

           struct B{
                  char e[2];
                  int f;
                  double g;  
                  short h;
                  struct A i;
           };

    sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

    sizeof(B) = 48; 看看B的内存布局。

                        e         f       g                h                      i 
    B的内存布局:11* *,1111,11111111, 11 * * * * * *,1111* * * *, 11111111, 1111 * * * * 。 i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。

    以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗。

                        a       b               c
    A的内存布局:1111,11111111,1111

                        e    f       g               h   i 
    B的内存布局:11,1111,11111111,11,1111, 11111111, 1111 

     那#pragma pack(2)的结果又是多少呢?#pragma pack(4)呢?留给大家自己思考吧,相信没有问题。

     还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取sizeof值。C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

    使用位域的主要目的是压缩存储,其大致规则为: 
    1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
    2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
    3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式; 
    4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 
    5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

    还是让我们来看看例子。

    例4:struct A{ 
                   char f1 : 3; 
                   char f2 : 4; 
                   char f3 : 5; 
           };

                        a      b          c
    A的内存布局:111,1111 *,11111 * * *

    位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。

    例5:struct B{ 
                   char f1 : 3; 
                   short f2 : 4; 
                   char f3 : 5; 
           };

    由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。

    例6:struct C{ 
                   char f1 : 3; 
                   char f2; 
                   char f3 : 5; 
           };

    非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。

    考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。

    最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

    ————————————————————————————————————————————————————————————————————————————— 无他,唯手熟尔。。。
  • 相关阅读:
    securecrt 中文乱码解决方案
    linux文件压缩、下载命令
    weinre调试
    linux查看当前目录命令
    linux下清除缓存文件并重启tomcat
    undefined加引号和不加引号的区别
    web/wap微博分享链接
    linux查找文件内容
    MySQL 5.1 安装过程中报apply security setting错误的解决办法 收藏
    Sleep Mode For WSN of Jennic
  • 原文地址:https://www.cnblogs.com/ZXNblog/p/4049181.html
Copyright © 2020-2023  润新知