结构体内存对齐与位域

C语言结构体的对齐问题
C语言的结构体对齐问题是常见类型的题目,在笔试和面试中也是常考的题目,例如:
struct
{
   int a;
   char b;
   long c;
   short d;
}s;
问sizeof(s) = ?
我们假设在32位机器下,要解决这个问题我们必须明确各种数据类型占用的空间是多大:
     int 类型:4字节;
  long类型:4字节;
double类型:8字节;
float 类型:4字节;
short 类型:2字节;
char  类型:1字节;
所有指针类型:4字节;
int a[] = {1,2,3,4}:sizeof(a) = 16。
好,明确了上述就开始进入正题:
一般情况下,即没有#pragma pack宏定义和使用位域的情况下,结构体对齐一般满足三个原则:
1.普通数据成员:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
2.结构体数据成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储)。
3.结构体的总大小,必须是其内部最大元素占据字节的整数倍。
struct {
int a;
int b;
int c;
}A;
sizeof(A) = 12;//简单,内部成员均为int型,自动对齐。
  struct{
           char a;
           int b;
           short c;
           double d;
    }B;
sizeof(B)=?
a占用一个字节:
1
b占用四个字节,根据原则1,b起始地址必须为4的整数倍,现在为1,不符合条件,因此必须填充:
1*** 1111
c占用二个字节,符合原则1,根据原则3,总位数应该为4的整数倍,因此最后应该填充两个字节:
1*** 1111 11**
d占用八个字节,根据原则1,起始地址应该为8的整数倍,现在为12,需要填充:
1*** 1111 11** **** 11111111
所以sizeif(B) = 24
typedef struct
{
   int a;
   double b;
   float c;
}A1;
struct
{
  char e[2];
  int  f;
  double g; 
  short h;
  A1 sa;
}B1;
sizeof(A1)=?;sizeof(B1)=?
先来看A1,
a占用四个字节:
1111
b占用八个字节,根据原则1,b的起始地址为8的整数倍,现在为1,应该填充七个字节:
1111**** 11111111
c占用四个字节,现在满足原则1,
1******* 11111111 1111
但是根据原则3,A1的总大小必须是8的倍数,因此,还要填充四个字节:
1111****11111111 1111****
所以sizeof(A1) = 24;
现在来看B1,
首先e中有两个char型元素,占两个字节:
11
int型的f占据四个字节,根据原则1,f的起始地址为4的整数倍,填充:
11**1111
double类型的g占八个字节,根据原则1,g的起始地址为8的整数倍,符合,不用填充:
11** 11111111 1111
short类型h占据二个字节,符合原则1,根据原则3,填充:
11** 11111111 11******
最后sa占据24个字节,其内部最大成员的字节数为8,因此sa的起始地址为8的整数倍,符合
11** 11111111 11****** 111111111111111111111111
所有sizeof(B1) = 48;
上述都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义。比如上面的结构体前加#pragma pack(1),则sizeof(A1) = 16; sizeof(B1) = 32;
有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐,即可理解为内部成员占用字节的和。
那么#pragma pack(2)呢?
代表按照两个字节对齐,这种情况下,sizeof(A1) = 16; sizeof(B1) = 32
#pragma pack(4)宏定义下:

sizeof(A1) = 16; sizeof(B1) = 36


最后介绍下结构体中的位域。
字面理解位域就是说某些数据元素并不需要占据一整个字节,只需要占据几位,例如数字8,就只需要占据一个字节的四位即可表示。所谓位域,就是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。
例如下面的定义:
struct
{
 int a:8;
 int b:2;
 int c:6;
}A; 
位域a占8位,位域b占2位,位域c占6位,总共占据两个字节。
关于位域的对齐,有如下几点:

1. 如果相邻位域类型相同,位宽之和小于类型的sizeof大小,则后面的字段紧邻前一个字段存储,直到不能容纳为止;
2. 如果相邻位域类型相同,位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3. 如果相邻位域类型不同,则vc6采取不压缩方式,dev-c++ 和GCC都采取压缩方式。依然满足结构体内存对齐三个原则中的原则1,在不压缩方式下,如果前一个位域类型有填充,后面的位域类型和前面的位域类型不相同,则填充的区域不能存放放后面的位域,需另开辟空间;而在压缩方式下,填充的区域如果可以放下后者位域,则存放,放不下的情况下再另开辟空间。
4.一个位域必须存储在同一个字节中,不能跨字节;
5.如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
 struct
    {
          char a:2;
          int b:4;
          int c:4;
    }A;
在不压缩条件下:char类型占据一个字节,而a占用其中两位;根据原则1,b的类型int占据四个字节,应该从4的整数倍处开始存放,所以char后应该填充三个字节,这三个字节虽然能够容纳b,但是必须另开空间,再开辟四个字节的空间存放int,而b占据四个字节,后面c的类型和b的类型相同,所以紧邻b存储,占据四个字节。所以在vc下,sizeof(A) = 8;
压缩条件下:char类型占据一个字节,而a占用其中两位;根据原则1,b的类型int占据四个字节,应该从4的整数倍处开始存放,所以char后应该填充三个字节,这三个字节能够容纳b和c,因此不需要重新开辟空间,直接在这三个字节上存储,所以sizeof(A) = 4;







posted @ 2015-05-04 16:21  sunp823  阅读(1696)  评论(0编辑  收藏  举报