解析C语言中结构体struct的对齐问题
首先看一下结构体对齐的三个概念值:
数据类型的默认对齐值(自身对齐):
1.基本数据类型:为指定平台上基本类型的长度。如在32位机器中,char对齐值为1,short为2,int,float为4,double为8;
结构体:其数据成员中默认对齐值最大的那个值。
2.指定对齐值:#pragma pack (value)时的指定对齐值value。
3.数据类型的有效对齐值:默认对齐值和指定对齐值中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的“偏移量%N=0”。而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放(对于非对齐成员需要在其前面填充一些字节,保证其在对齐位置上),结构体本身也要根据自身的有效对齐值圆整(就是结构体总长度需要是结构体有效对齐值的整数倍)。
通过上面的分析,对结构体进行字节对齐,我们需要知道四个值:
- 指定对齐值:代码中指定的对齐值,记为packLen;
- 默认对齐值:结构体中每个数据成员及结构体本身都有默认对齐值,记为defaultLen;
- 成员偏移量:即相对于结构体起始位置的长度,记为offset;
- 成员长度:结构体中每个数据成员的长度(注结构体成员为补齐之后的长度),记为memberLen。
及两个规则:
1.对齐规则:
offset % vaildLen = 0,其中vaildLen为有效对齐值vaildLen = min(packLen, defaultLen);
2.填充规则:
如成员变量不遵守对齐规则,则需要对其补齐;在其前面填充一些字节保证该成员对齐。需填充的字节数记为padLen:
padLen = getPadLen(offset , defaultLen); int getPadLen(int offsetLen, int defaultLen) { int vaildLen = min(packLen,defaultLen); if(0 == vaildLen || 0 == offsetLen % vaildLen) { return 0; } return vaildLen - (offsetLen % vaildLen); }
结构体对齐算法思想:深度优先填充
先对齐内层结构体;
对每个数据成员计算其defaultLen、memberLen和offset;
再遍历每个数据成员时计算;
对于基本数据类型成员defaultLen=memberLen;对于结构体成员defaultLen等于它的所有成员的最大的memberLen;
遍历时对成员的memberLen进行累加,得到当前成员的offsetLen;
运用对齐及填充规则:在当前结构体成员前填充padLen个字节;
举例说明:
struct{ short a; short b; short c; }A; sizeof(A) = 6;
(vc6与gcc相同)
struct{ long a; short c; }A; sizeof(A) = 8;
(vc6与gcc相同), 它的内存分配为: a1 a2 a3 a4 , c1 c2 x x(a1为a的第一个字节,x为补齐字节,下同)
struct{ int a; char b; short c; }A; sizeof(A) = 8;
A的内存分配为:
a1 a2 a3 a4, b1 x c1 c2
struct{ char a; int b; short c; }A1; sizeof(A1) = 12;
(vc6与gcc相同)
A1的内存分配为:
a1 x x x, b1 b2 b3 b4, c1 c2 x x
下面是更复杂的情况,结构体作为成员
struct{ int a; doubl b; short c; }A; // sizeof(A) = 24 (vc6与gcc相同) struct{ char a,b; int c; double d; short e; struct A h; }B; sizeof(B) = 48 //(vc6与gcc相同)
A的内存分布:
a1 a2 a3 a4 x x x x, b1 b2 b3 b4 b5 b6 b7 b7, c1 c2 x x x x x x
B的内存分布:
a1 b1 x x, c1 c2 c3 c4 , d1 d2 d3 d4 d5 d6 d7 d8, e1 e2 x x x x