Chinaunix首页 | 论坛 | 博客
  • 博客访问: 653487
  • 博文数量: 150
  • 博客积分: 4070
  • 博客等级: 中校
  • 技术积分: 1795
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-23 21:44
文章分类

全部博文(150)

文章存档

2012年(1)

2011年(123)

2010年(26)

分类: C/C++

2011-08-15 09:53:35

关于C语言中的结构体对齐。

  (1)什么是字节对齐

  一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即: 存放起始地址 % n = 0 对于结构体而言,这个 n 取其成员种的数据类型占空间的值最大的那个。

  (2)为什么要字节对齐

  内存空间是按照字节来划分的,从理论上说对内存空间的访问可以从任何地址开始,但是在实际上不同架构的CPU为了提高访问内存的速度,就规定了对于某些类型的数据只能从特定的起始位置开始访问。这样就决定了各种数据类型只能按照相应的规则在内存空间中存放,而不能一个接一个的顺序排列。

  举个例子,比如有些平台访问内存地址都从偶数地址开始,对于一个int(假设32位系统),如果从偶数地址开始的地方存放,这样一个读周期就可以读出这个int数据,但是如果从奇数地址开始的地址存放,就需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到这个int数据,这样明显降低了读取的效率。

  (3)如何进行字节对齐

  每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(不指定则取默认值)中较小的一个对齐,并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

  这个规则有点苦涩,可以把这个规则分解一下,前半句的意思先获得对齐值后与指定对齐值进行比较,其中对齐值获得方式如下:

  1. 数据类型的自身对齐值为:对于char型数据,其自身对齐值为1,对于short型为2,对于int, long, float类型,其自身对齐值为4,对于 double 类型其自身对齐值为8,单位为字节。

  2.结构体自身对齐值:其成员中自身对齐值最大的那个值。

  其中指定对齐值获得方式如下:

  #pragma pack (value)时的指定对齐值value

  未指定则取默认值。

  后半句的意思是主要是针对于结构体的长度而言,因为针对数据类型的成员,它仅有一个对齐参数,其本身的长度、于这个对齐参数,即1倍。对于结构体而言,它可能使用了多种数据类型,那么这句话翻译成对齐规则: 每个成员的起始地址 % 自身对齐值 = 0,如果不等于 0 则先补空字节直至这个表达式成立。

  换句话说,对于结构体而言,结构体在在内存的存放顺序用如下规则即可映射出来:

  ()每个成员的起始地址 % 每个成员的自身对齐值 = 0,如果不等于 0 则先补空字节直至这个表达式成立;

  ()结构体的长度必须为结构体的自身对齐值的整数倍,不够就补空字节。

  举个例子:

#pragmapack(8)
structA{
  chara;
  longb;
}
;
structB{
  chara;
  structAb;
  longc;
}
;
structC{
  chara;
  structAb;
  doublec;
}
;
structD{
  chara;
  structAb;
  doublec;
  intd;
}
;
structE{
  chara;
  intb;
  structAc;
  doubled;  
}
;

  对于 struct A 来说,对于char型数据,其自身对齐值为1,对于long类型,其自身对齐值为4, 结构体的自身对齐值取其成员最大的对齐值,即大小4。那么struct A 在内存中的顺序步骤为:

  (1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

  (2) long b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x0007.

  (3)此时成员都存放结束,结构体长度为8,为结构体自身对齐值的2倍,符合条件().

  此时满足条件()和条件()struct A 中各成员在内存中的位置为:a*** b ,sizeof(struct A) = 8(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占四位)

  对于struct B,里面有个类型为struct A的成员b自身对齐值为4,对于long类型,其自身对齐值为4. struct B的自身对齐值为4。那么structB 在内存中的顺序步骤为:

  (1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

  (2) struct A b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x00011.

  (3) long c,地址起始位置从0x000012开始, 因为 0x0012 % 4 = 0,其地址范围为0x00012~0x0015.

  (4)此时成员都存放结束,结构体长度为16,为结构体自身对齐值的4倍,符合条件().

  此时满足条件()和条件()struct B 中各成员在内存中的位置为:a*** b c ,sizeof(struct C) = 24(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占四位)

  对于struct C,里面有个类型为struct A的成员b自身对齐值为4,对于double 类型,其自身对齐值为8. struct C的自身对齐值为8。那么struct C 在内存中的顺序步骤为:

  (1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。

  (2) struct A b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x00011.

  (3) double c,地址起始位置不能从0x000012开始, 因为 0x0012 % 8 != 0,所以先补空字节,直到0x000015结束,即补4个字节的空字节,从0x00016开始存放c,其地址范围为0x00016~0x0023.

  (4)此时成员都存放结束,结构体长度为24,为结构体自身对齐值的3倍,符合条件().

  此时满足条件()和条件()struct C 中各成员在内存中的位置为:a*** b **** c ,sizeof(struct C) = 24(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占八位)

  对于struct D,自身对齐值为8。前面三个成员与 struct C 是一致的。对于第四成员d,因为 0x0024 % 4 = 0, 所以可以从0x0024开始存放d, 其地址范围为0x00024~0x00027.此时成员都存放结束,结构体长度为2828 不是结构体自身对齐值8的倍数,所以要在后面补四个空格,即在0x0028~0x0031上补四个空格。补完了,结构体长度为32, 为结构体自

  身对齐值的4被,,符合条件().

  此时满足条件()和条件()struct D 中各成员在内存中的位置为:a*** b **** c d **** ,sizeof(struct D) = 32(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占八位, d占四位)

  对于struct E 中各成员在内存中的位置为:a*** b c d, sizeof(struct E) = 24(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占四位,c占八位, d占八位)

  通过struct D struct E 可以看出,在成员数量和类型一致的情况,后者的所占空间少于前者,因为后者的填充空字节要少。如果我们在编程时考虑节约空间的话,应该遵循将变量按照类型大小从小到大声明的原则, 这样尽量减少填补空间。另外,可以在填充空字节的地方来插入reserved成员, 例如

struct A
{
  char a;
  char reserved[3];
  int b;
}

  这样做的目的主要是为了对程序员起一个提示作用,如果不加则编译器会自动补齐。

以上方式都是按默认的对齐方式进行的,同时也可以自己设定对齐大小

语法设置:#pragma packvalue

虽然可以设置,但其值value的大小如果大于或等于了结构成员本身的大小,那value值将不起作用。如果value的大小小于结构成员xsizeof大小,那么成员x的存储起始地址将按value对齐,即起始地址是value的倍数。这个是#pragma packvalue)对数据成员本身对齐的影响。另外结构体的大小最后还要是结构体本身对齐,默认情况下,结构体自身对齐的大小是所有成员中sizeof最大的值,如果value大于所有成员的sizeof值,那么value对结构体自身对齐是没有影响,还是按默认方式对其,如果value值不大于所有成员的大小,则最后结构体的大小将是value的倍数。

 

例子:

Struct A

{

      Char ch;

      Int I;

}

按照默认的对齐方式为:0ch,然后按照成员本身对齐,于是补充三个空字节,然后从47存放I,于是共占8字节,而结构体的对齐是4842倍,因此A最后的大小为8.

 

#pragma pack(2)

Struct A

{

      Char ch;

      Int I;

}

首先存放ch,由于2大于了sizeof(ch)的大小,于是ch按照默认的对齐方式存放,于是0地址处放ch,然后存放i,由于2小于sizeof(int)=4,因此存放i的首地址按照2对齐,于是补充一个空字节,从25存放i,于是共占6字节。由于2不大于所有结构成员的大小,因此结构按2对齐,由于623倍,因此A的大小为6.

阅读(1834) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~