C语言结构体对齐

来源:互联网 发布:燕青对李逵知乎 编辑:程序博客网 时间:2024/06/05 06:43
C语言结构体对齐 
C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容
虽然很基础,但一不小心就会弄错。写出一个 struct,然后 sizeof,你会不会经
常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么
回事呢? 
 
    开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚
的不多。结构体到底怎样对齐? 
 
    有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下
前人的经验(在没有#pragma pack宏的情况下): 
 
    原则 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 ; 
 
 
               struc t { 
                   long a1; 
                   short a2 ; 
                  }B ; 
 
       size of (A ) = 6; 这个很好理解,三个 short都为 2。 
 
       size of (B ) = 8; 这个比是不是比预想的大 2个字节?long为 4, short为 2,C语言结构体对齐 
C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容
虽然很基础,但一不小心就会弄错。写出一个 struct,然后 sizeof,你会不会经
常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么
回事呢? 
 
    开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚
的不多。结构体到底怎样对齐? 
 
    有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下
前人的经验(在没有#pragma pack宏的情况下): 
 
    原则 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 ; 
 
 
               struc t { 
                   long a1; 
                   short a2 ; 
                  }B ; 
 
       size of (A ) = 6; 这个很好理解,三个 short都为 2。 
 
       size of (B ) = 8; 这个比是不是比预想的大 2个字节?long为 4, short为 2,
整个为 8,因为原则 3。 
 
       例 2:struct A{ 
                    int a ; 
                    char b; 
                    short c ; 
                    }; 
 
 
               struc t B { 
                   char b; 
                   int a; 
                   short c ; 
                    }; 
 
       size of (A ) = 8; in t为 4, char为 1, short为 2,这里用到了原则 1和原则 3。 
 
       size of (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; 
                     f loat c; C语言结构体对齐 
C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容
虽然很基础,但一不小心就会弄错。写出一个 struct,然后 sizeof,你会不会经
常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么
回事呢? 
 
    开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚
的不多。结构体到底怎样对齐? 
 
    有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下
前人的经验(在没有#pragma pack宏的情况下): 
 
    原则 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 ; 
 
 
               struc t { 
                   long a1; 
                   short a2 ; 
                  }B ; 
 
       size of (A ) = 6; 这个很好理解,三个 short都为 2。 
 
       size of (B ) = 8; 这个比是不是比预想的大 2个字节?long为 4, short为 2,
整个为 8,因为原则 3。 
 
       例 2:struct A{ 
                    int a ; 
                    char b; 
                    short c ; 
                    }; 
 
 
               struc t B { 
                   char b; 
                   int a; 
                   short c ; 
                    }; 
 
       size of (A ) = 8; in t为 4, char为 1, short为 2,这里用到了原则 1和原则 3。 
 
       size of (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; 
                     f loat c; 
                    }; 
 
                struc t B { 
                     char e[ 2] ; 
                     int f ; 
                     double g;   
                     short h; 
                     struc t A i; 
                    }; 
 
       size of (A ) = 24; 这个比较好理解,int为 4,double为 8,float为 4,总长
为 8的倍数,补齐,所以整个 A为 24。 
 
       size of (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大小,
则后面的字段将紧邻前一个字段存储,直到不能容纳为止;  C语言结构体对齐 
C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容
虽然很基础,但一不小心就会弄错。写出一个 struct,然后 sizeof,你会不会经
常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么
回事呢? 
 
    开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚
的不多。结构体到底怎样对齐? 
 
    有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下
前人的经验(在没有#pragma pack宏的情况下): 
 
    原则 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 ; 
 
 
               struc t { 
                   long a1; 
                   short a2 ; 
                  }B ; 
 
       size of (A ) = 6; 这个很好理解,三个 short都为 2。 
 
       size of (B ) = 8; 这个比是不是比预想的大 2个字节?long为 4, short为 2,
整个为 8,因为原则 3。 
 
       例 2:struct A{ 
                    int a ; 
                    char b; 
                    short c ; 
                    }; 
 
 
               struc t B { 
                   char b; 
                   int a; 
                   short c ; 
                    }; 
 
       size of (A ) = 8; in t为 4, char为 1, short为 2,这里用到了原则 1和原则 3。 
 
       size of (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; 
                     f loat c; 
                    }; 
 
                struc t B { 
                     char e[ 2] ; 
                     int f ; 
                     double g;   
                     short h; 
                     struc t A i; 
                    }; 
 
       size of (A ) = 24; 这个比较好理解,int为 4,double为 8,float为 4,总长
为 8的倍数,补齐,所以整个 A为 24。 
 
       size of (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 f 1 : 3 ;  
                     char f 2 : 4;  
                     char f 3 : 5;  
                     }; 
                      a         b         c 
       A的内存布局:111,    1111 *,   11111 * * * 
 
       位域类型为 char,第 1个字节仅能容纳下 f1和 f2,所以 f2被压缩到第 1
个字节中,而 f3只能从下一个字节开始。因此 sizeof(A)的结果为 2。 
 
       例 5:struct B{  
                    ch ar f 1 : 3 ;  
                    short f 2 : 4;  
                    ch ar f 3 : 5 ;  
                    }; 
       由于相邻位域类型不同,在 VC6中其 sizeof为 6,在 Dev-C++中为 2。 
 
       例 6:struct C{  
                     char f 1 : 3;  
                     char f 2;  
                    ch ar f 3 : 5 ;  
                    }; 
       非位域字段穿插在其中,不会产生压缩,在 VC6和 Dev-C++中得到的大
小均为 3。 
       考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是
不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使
用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省
了时间。假设一个体系结构的字长为 w,那么它同时就假设了在这种体系结构上
对宽度为 w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对 w
位数据操作的效率来考虑的。有兴趣的可以 google一下,人家就可以跟你解释
的,一大堆的道理。 
 
       最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把
占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一
些对齐空间。  
原创粉丝点击