简单易懂的C结构体对齐原则
结构体的对齐原则非常简单,只需要理解两个点就能透彻理解结构体对齐。分别是:
- 结构体内成员的对齐长度,该长度就是该成员的类型长度;
- 结构体的对齐长度,该长度默认情况下是成员类型长度最长的那成员的类型长度;
详解
-
原则1:(没有使用#pragma pack指令)
结构体每个成员都有自己的对齐原则,该成员的对齐原则为该成员的类型长度的整倍数。
A. 简单示例如下(暂时先不关注结构体的总长度):
struct A { char a; short b; int c; }
在上述结构体中,
- 成员a的对齐长度为1,前面没有成员,自然就是第一个;
- 成员b的对齐长度为2,此时已经存在的结构体长度为1,1/2 不是整倍数,那么就需要进行填充;填充一个字节就能是2的整倍数,算法就是(1%2)+1=2, 因此成员b放进去之后呢,该结构体的长度就为4了;
- 成员c的对齐长度为4,前面的长度已经就是4了,4/4为整倍数,因此不需要填充。
- 那么此时结构体的长度就为8
B. 复杂示例如下(暂时先不关注结构体的总长度):
struct B { short a; char b[11]; short c[10]; double d; int e; }
这个是一个稍微复杂的例子,不过同样很简单。
-
成员a的对齐长度为2,占两个字节,
-
成员b的对齐长度为1,虽然b为数组,但是这里可以看成是多个连续的变量,因此他的对齐长度为1;当前结构体的长度为2,2/1是整倍数,因此不需要填充,然后当前结构体的长度为2+11 = 13;
-
成员c的对其长度为2,同理,看做是一个连续的short成员;此时,当前结构体的长度为13,当前c的对齐长度为2, 13 /2 = 6.5,不是一个整倍数,那么就需要填充,算法为(2-(13%2))+13 = 14,因此成员c的起始位置为14,此时结构体的长度为14+ 10*2 = 34;((2-(13%2))解释,2为当前的对齐长度,13%2为取余数,那么用2减去余数就是差多少个到2,注意看下面以8为对齐长度的例子)
(好了,如果认真看到这里的基本就明白是怎么回事了。如果自己还能动手算算的更棒了);
-
成员d的对齐长度为8,此时结构体的长度为34,34/8 不能被整除,于是需要填充,填充字节数为 :8-(34%8), 起始位置:(8-(34%8))+34=40,因此成员d的起始位就是40了;(8-(34%8),34/8取余为2, 8-2当前的起始位置到8 还差6个字节)
-
成员e的对齐长度为4,此时结构体的 48, 48/4可以整除,所以不需要填充。因此成员e的起始位置就为48了。因此此时结构体的长度就位52了。
-
结构体中有结构体的情况看最下面的实例.
-
原则2:(可以说是非常简单了)
在没有使用#pragma pack(n)的情况下,结构体的总长度默认以最长的成员的长度作为对齐长度的。
此处以上两个例子继续为例好了。上述的例子里面我们都没有计算整个结构体的总长度
-
例子A
当前A 的结构体长度为8,当前结构体中最大长度成员的对齐是c,对齐长度为4,按照上述的原则,当前结构体的对齐长度为4,那么8/4可以整除,那么当前结构体不需要填充。
-
例子B
当前B的结构体长度为52,然后结构体中最长的成员为d,该成员的长度为8,于是当前结构体的对齐长度为8,那么52/8 = 6.5,不是一个整倍数。那么说明需要填充操作。填充字节数:8 - (52%8), 8 - 4 = 4,则当前结构体需要填充4个字节,因此最终结构体B的总长度就为 52 + 4 = 56个字节。
-
#pragma pack(n), n ∈ {1
,2
,4
,8
,16
}
#pragma pack改变的是结构体内所有成员的对齐长度原则,意思就是说,如果使用了该指令,上例中结构体成员的对齐长度也由该指令指定。
例如示例A,如果加上了#pragma pack(1)
,结构体长度则为 1 + 2 + 4 = 7
示例B呢么,如果加上了#pragma pack(1)
, 那么结构体的长度则为:2+11+2*10+8+4=45
那么如果示例B来个pragma pack(3)
, 那就不好意思了。该指令的n的取值范围为 {1
, 2
, 4
, 8
, 16
}中的任意一个,其他任何非该取值范围的值都将使用当前的默认对齐当时。
#pragma pack(push/pop)
该指令代表pack的开始和结束,在此范围内的#pragma pack(n)
离开该范围之后,就会恢复到#pragma pack(push)
之前的对齐值。
常见理解误区
-
结构体的对齐长度为 最长成员类型长度;
解答:以上概念没什么问题,只是以上原则只是对上文中原则2的解释,所说的就只是结构体的对齐长度,而没有包含成员的对齐原则; 结构体的对齐长度可以通过预编译指令#pragma pack(n)进行改变;被#pragma pack(n)包含的结构体的成员和结构体的对齐值都为n;
-
结构体中包含结构体的情况中,那么外结构体的对齐长度为内结构的长度;
解答:严格来说,这个说法是不正确的。在多层结构体中,外结构体的对齐长度仍然遵循原则2,内结构体的对齐长度为该结构体的对齐长度;
好了,不会算的多看两遍。动手就好。