结构体对齐问题1

简介: C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢? 开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐?

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

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

      原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

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

      例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)呢?留给大家自己思考吧,相信没有问题。

       这里,#pragma pack (value)宏指令,value就是指定的对齐值。
     

      还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取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一下,人家就可以跟你解释的,一大堆的道理。

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

相关文章
|
编译器 Linux C语言
什么是结构体和结构体的对齐规则
什么是结构体,为什么会用到结构体?C语言本身存在一些内置数据类型(比如int char float double 数组等),但这些不能满足我们的需要,我们创建了结构体来自定义自己需要的类型。
108 0
|
2月前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
7月前
|
存储 编译器 C语言
【C语言】结构体的大小是如何计算的?(结构体对齐)
【C语言】结构体的大小是如何计算的?(结构体对齐)
126 0
|
7月前
|
存储
联合体和结构体对齐
联合体和结构体对齐
|
存储 编译器 C语言
结构体对齐+联合体+位段
结构体对齐+联合体+位段
110 1
|
编译器 Linux C++
结构体的内存对齐
结构体的内存对齐
|
存储 算法 Linux
关于c语言内存地址对齐的一点思考
关于c语言内存地址对齐的一点思考
132 0
|
编译器 C++
结构体内存对齐问题
结构体重点😃 1.结构体内存对齐问题,是在计算结构体的大小时,对结构体成员在内存中的位置进行研究的问题。
|
存储 编译器 C++
【C/C++】结构体&内存对齐
【C/C++】结构体&内存对齐
185 0