C语言字节对齐

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一下,人家就可以跟你解释的,一大堆的道理。

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

参考文章:

1.http://hi.baidu.com/jiyeqian/blog/item/c14e52c2a43015160ff47773.html

2.http://apps.hi.baidu.com/share/detail/125013

 

 

 

 

C编译器的缺省字节对齐方式(自然对界)


在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。

在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储(成员之间可能有插入的空字节),第一个成员的地址和整个结构的地址相同。

 

C编译器缺省的结构成员自然对界条件为“N字节对齐”,N即该成员数据类型的长度。如int型成员的自然对界条件为4字节对齐,而double类型的结构成员的自然对界条件为8字节对齐。若该成员的起始偏移不位于该成员的“默认自然对界条件”上,则在前一个节面后面添加适当个数的空字节。

 

C编译器缺省的结构整体的自然对界条件为:该结构所有成员中要求的最大自然对界条件。若结构体各成员长度之和不为“结构整体自然对界条件的整数倍,则在最后一个成员后填充空字节。

例子1(分析结构各成员的默认字节对界条界条件和结构整体的默认字节对界条件):

  
  
struct Test
{
char x1; // 成员x1为char型(其起始地址必须1字节对界),其偏移地址为0

char x2; // 成员x2为char型(其起始地址必须1字节对界,其偏移地址为1

float x3; // 成员x3为float型(其起始地址必须4字节对界),编译器在x2和x3之间填充了两个空字节,其偏移地址为4

char x4; // 成员x4为char型(其起始地址必须1字节对界),其偏移地址为8
};


因为Test结构体中,最大的成员为flaot x3,因些此结构体的自然对界条件为4字节对齐。则结构体长度就为12字节,内存布局为1100 1111 1000。

例子2:

 

  
  
#include < stdio.h >
// #pragma pack(2)
typedef struct
{
int aa1; // 4个字节对齐 1111
char bb1; // 1个字节对齐 1
short cc1; // 2个字节对齐 011
char dd1; // 1个字节对齐 1
} testlength1;
int length1 = sizeof (testlength1); // 4个字节对齐,占用字节1111 1011 1000,length = 12

typedef struct
{
char bb2; // 1个字节对齐 1
int aa2; // 4个字节对齐 01111
short cc2; // 2个字节对齐 11
char dd2; // 1个字节对齐 1
} testlength2;
int length2 = sizeof (testlength2); // 4个字节对齐,占用字节1011 1111 1000,length = 12


typedef struct
{
char bb3; // 1个字节对齐 1
char dd3; // 1个字节对齐 1
int aa3; // 4个字节对齐 001111
short cc23 // 2个字节对齐 11

} testlength3;
int length3 = sizeof (testlength3); // 4个字节对齐,占用字节1100 1111 1100,length = 12


typedef struct
{
char bb4; // 1个字节对齐 1
char dd4; // 1个字节对齐 1
short cc4; // 2个字节对齐 11
int aa4; // 4个字节对齐 1111
} testlength4;
int length4 = sizeof (testlength4); // 4个字节对齐,占用字节1111 1111,length = 8


int main( void )
{
printf( " length1 = %d./n " ,length1);
printf( " length2 = %d./n " ,length2);
printf( " length3 = %d./n " ,length3);
printf( " length4 = %d./n " ,length4);
return 0 ;
}

 

 

改变缺省的对界条件(指定对界)
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。


这时,对齐规则为:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

因此,当使用伪指令#pragma pack (2)时,Test结构体的大小为8,内存布局为11 11 11 10。


需要注意一点,当结构体中包含一个子结构体时,子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中,比较小的那个进行进行对齐。例子如下:

#pragma pack(8)
struct s1{
short a;
long b;
};


struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()

sizeof(s2)的结果为24。S1的内存布局为1100 1111,S2的内存布局为1000 1100 1111 0000 1111 1111。

例子:

 

  
  
#include < stdio.h >
#pragma pack(2)
typedef struct
{
int aa1; // 2个字节对齐 1111
char bb1; // 1个字节对齐 1
short cc1; // 2个字节对齐 011
char dd1; // 1个字节对齐 1
} testlength1;
int length1 = sizeof (testlength1); // 2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef struct
{
char bb2; // 1个字节对齐 1
int aa2; // 2个字节对齐 01111
short cc2; // 2个字节对齐 11
char dd2; // 1个字节对齐 1
} testlength2;
int length2 = sizeof (testlength2); // 2个字节对齐,占用字节10 11 11 11 10,length = 10


typedef struct
{
char bb3; // 1个字节对齐 1
char dd3; // 1个字节对齐 1
int aa3; // 2个字节对齐 11 11
short cc23 // 2个字节对齐 11

} testlength3;
int length3 = sizeof (testlength3); // 2个字节对齐,占用字节11 11 11 11,length = 8


typedef struct
{
char bb4; // 1个字节对齐 1
char dd4; // 1个字节对齐 1
short cc4; // 2个字节对齐 11
int aa4; // 2个字节对齐 11 11
} testlength4;
int length4 = sizeof (testlength4); // 2个字节对齐,占用字节11 11 11 11,length = 8


int main( void )
{
printf( " length1 = %d./n " ,length1);
printf( " length2 = %d./n " ,length2);
printf( " length3 = %d./n " ,length3);
printf( " length4 = %d./n " ,length4);
return 0 ;
}

 

另外,还有如下的一种方式:

· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。


以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值