结构体变量和基础类型变量的对齐准则

首先,一句话回答你的问题: 结构体变量的对齐原则是为了优化CPU访问内存的效率,其核心是让每个成员变量的起始地址都是其自身大小的整数倍,同时整个结构体的总大小是其最宽成员大小的整数倍。

二、 对齐原则的详细讲解

在C语言中,数据对齐遵循以下三条基本原则:

  1. 成员对齐规则 (Member Alignment Rule): 结构体的第一个成员变量,其存放的起始地址与结构体变量的起始地址相同。从第二个成员变量开始,每个成员变量的起始地址必须是该成员数据类型自身大小编译器默认对齐数较小值的整数倍。

    • 在大多数32位/64位系统中,默认对齐数通常是4或8。考研中若无特殊说明,可以认为编译器默认对齐数足够大(例如8或更大),因此可以直接简化为:每个成员的起始地址是其自身大小的整数倍
  2. 结构体总大小对齐规则 (Struct Padding Rule): 结构体成员全部放置完毕后,其总大小必须是结构体中所有成员数据类型中最大那个成员的大小编译器默认对齐数较小值的整数倍。不足的部分,将在结构体末尾进行填充(Padding)。

    • 同样,考研中可简化为:结构体总大小必须是其最宽成员大小的整数倍
  3. 嵌套结构体对齐规则 (Nested Struct Rule): 如果结构体中包含了其他结构体,那么这个嵌套的结构体成员的起始地址必须是其内部最宽成员的大小编译器默认对齐数较小值的整数倍。整个结构体的总大小仍然遵循第二条规则。

    • 简化理解:将嵌套的结构体看作一个整体成员,它的“自身大小”就是它内部最宽成员的大小。

注意: #pragma pack(n) 是一个预处理指令,可以用来改变编译器默认的对齐数。考研中偶尔会考察这种情况,此时,上述规则中的“编译器默认对齐数”就要换成 n。规则1、2、3中的“较小值”的比较逻辑变得至关重要。

三、 图示讲解

基础类型变量的对齐

基础类型变量(如 int, char, double)的对齐相对简单。假设内存地址从0开始,一个 int 变量(通常4字节)会存放在地址是4的倍数的地方(如0, 4, 8, ...);一个 short(通常2字节)会存放在地址是2的倍数的地方(如0, 2, 4, ...)。

结构体对齐示例

例题1: 计算下面定义的结构体 struct Test 在32位系统下的大小。

C

struct Test {
    char c1;
    int i;
    char c2;
};

分析过程:

假设该结构体变量 t 的起始地址为 0x0000

  1. 放置 c1 (char, 1字节):

    • c1 是第一个成员,放在结构体首地址。

    • 起始地址: 0x0000

    • 占用空间: 0x0000

    • 当前结构体大小: 1字节。

    • 内存布局: | c1 |

  2. 放置 i (int, 4字节):

    • i 的自身大小为4字节。根据成员对齐规则,其起始地址必须是4的倍数。

    • 当前地址为 0x0001,不是4的倍数。因此,编译器会向后填充(Padding)3个字节。

    • 填充后,下一个可用地址为 0x0004

    • 起始地址: 0x0004

    • 占用空间: 0x0004 - 0x0007

    • 当前结构体大小: 1 (c1) + 3 (padding) + 4 (i) = 8字节。

    • 内存布局: | c1 | P | P | P | i | i | i | i | (P代表Padding)

  3. 放置 c2 (char, 1字节):

    • c2 自身大小为1字节。其起始地址必须是1的倍数。

    • 当前地址为 0x0008,是1的倍数,可以直接放置。

    • 起始地址: 0x0008

    • 占用空间: 0x0008

    • 当前结构体大小: 8 + 1 = 9字节。

    • 内存布局: | c1 | P | P | P | i | i | i | i | c2 |

  4. 最终检查总大小 (结构体总大小对齐规则):

    • 结构体中所有成员(char, int, char)中最宽的是 int,大小为4字节。

    • 根据结构体总大小对齐规则,结构体总大小必须是4的倍数。

    • 当前大小为9字节,不是4的倍数。因此,需要在末尾填充3个字节,使其达到12字节(9 -> 10, 11, 12)。

    • 最终总大小: 12字节。

    • 最终内存布局: | c1 | P | P | P | i | i | i | i | c2 | P | P | P |


四、 边界情况与易错点总结

  1. 误用成员个数求和: 最常见的错误是简单地将所有成员的大小相加,完全忽略对齐和填充。

  2. 忽略结构体总大小对齐: 计算完所有成员布局后,忘记了检查并填充结构体末尾,使其总大小满足对齐要求。

  3. 嵌套结构体对齐基准混淆: 误将嵌套结构体的总大小作为其对齐基准,而不是其内部最宽成员的大小。

  4. 数组作为成员: struct { char c; int arr[3]; },此时数组 arr 被看作一个整体,但其对齐基准是其元素类型 int 的大小,即4字节。

  5. 指针作为成员: 无论什么类型的指针,在32位系统中大小为4字节,64位系统中为8字节。考研若无特别说明,按32位系统(4字节)计算。