• C++
  • 共用体的内存对齐教程

  • @ 2025-9-1 20:47:16

共用体的内存对齐教程

一、共用体(Union)基础回顾

在 C/C++ 中,共用体是一种特殊的数据结构,它允许多个不同类型的成员变量共享同一块内存空间,而非像结构体(Struct)那样为每个成员分配独立内存。这意味着:

  1. 共用体的所有成员从同一块内存的起始地址开始存储;
  2. 任意时刻只有一个成员能被有效访问(修改一个成员会覆盖其他成员的值);
  3. 共用体的大小由其“最大成员的大小”和“内存对齐规则”共同决定(核心考点)。

二、内存对齐的核心目的

内存对齐并非共用体特有,而是计算机硬件层面的优化规则,核心目的是提升 CPU 访问内存的效率

  • CPU 访问内存时,并非按“字节”逐个读取,而是按“对齐单位”(通常是 CPU 字长,如 4 字节、8 字节)批量读取;
  • 若数据未对齐(如一个 4 字节的 int 存放在地址 1-4),CPU 需要读取 2 次内存并拼接数据,效率降低;
  • 若数据对齐(如 int 存放在地址 0-3),CPU 仅需 1 次读取即可获取完整数据。

三、共用体内存对齐的 3 条核心规则

共用体的内存对齐需遵循以下 3 条规则,需结合实例理解:

规则 1:成员自身的对齐要求

每个成员变量都有其“自身对齐值”(即该类型的大小,如 char 为 1,int 为 4,double 为 8),成员必须存储在“自身对齐值的整数倍地址”上。
由于共用体所有成员共享起始地址,因此共用体的起始地址必须满足“所有成员中最大自身对齐值的整数倍”(否则最大成员无法对齐)。

规则 2:共用体整体大小的对齐要求

共用体的最终大小,必须是“所有成员中最大自身对齐值的整数倍”(称为“共用体对齐值”)。若最大成员的大小已满足该要求,则大小等于最大成员大小;若不满足,则需“补空字节”至对齐值的整数倍。

规则 3:成员共享内存,无偏移量

与结构体不同,共用体的成员无“偏移量”(结构体成员需按对齐规则依次偏移存储),所有成员的偏移量均为 0,直接覆盖同一块内存。

四、实例分析:手把手计算共用体大小

通过 3 个典型实例,掌握共用体内存对齐的计算方法:

实例 1:基础类型成员的共用体

#include <stdio.h>

// 定义共用体
union Test1 {
    char a;    // 自身对齐值 1,大小 1
    int b;     // 自身对齐值 4,大小 4
    double c;  // 自身对齐值 8,大小 8
};

int main() {
    printf("union Test1 的大小:%zu\n", sizeof(union Test1));
    return 0;
}

计算过程:

  1. 确定成员最大自身对齐值:double c 的对齐值为 8(所有成员中最大);
  2. 确定成员最大大小:double c 的大小为 8;
  3. 检查共用体大小是否满足对齐要求:8 是 8 的整数倍,无需补空;
  4. 最终大小:8 字节

运行结果:union Test1 的大小:8

实例 2:包含数组成员的共用体

#include <stdio.h>

union Test2 {
    char arr[5];  // 数组类型:自身对齐值 = 元素类型对齐值(char 为 1),大小 5
    int x;        // 自身对齐值 4,大小 4
};

计算过程:

  1. 确定成员最大自身对齐值:int x 的对齐值为 4(arr 的对齐值为 1,更小);
  2. 确定成员最大大小:char arr[5] 的大小为 5(int x 为 4,更小);
  3. 检查共用体大小是否满足对齐要求:5 不是 4 的整数倍,需补空至最近的 4 的整数倍(即 8);
  4. 最终大小:8 字节

运行结果:union Test2 的大小:8

实例 3:包含结构体成员的共用体

#include <stdio.h>

// 先定义结构体(需先计算结构体大小和对齐值)
struct Sub {
    char m;  // 对齐值 1,大小 1
    int n;   // 对齐值 4,大小 4
};

// 定义包含结构体的共用体
union Test3 {
    struct Sub s;  // 结构体成员:自身对齐值 = 结构体的对齐值(4),大小 = 结构体大小(8)
    double d;      // 对齐值 8,大小 8
};

第一步:计算结构体 struct Sub 的大小(结构体对齐规则):

  1. char m 占 1 字节,地址 0;
  2. int n 对齐值为 4,需存放在 4 的整数倍地址(地址 4-7),因此 m 后补 3 字节空;
  3. 结构体大小:1(m)+ 3(空)+ 4(n)= 8 字节,且 8 是最大对齐值 4 的整数倍,满足要求。

第二步:计算共用体 union Test3 的大小:

  1. 确定成员最大自身对齐值:double d 的对齐值为 8(struct Sub 的对齐值为 4,更小);
  2. 确定成员最大大小:struct Sub sdouble d 的大小均为 8;
  3. 检查对齐:8 是 8 的整数倍,无需补空;
  4. 最终大小:8 字节

运行结果:union Test3 的大小:8

五、共用体与结构体内存对齐的核心区别

对比维度 共用体(Union) 结构体(Struct)
内存分配方式 所有成员共享同一块内存,起始地址相同 为每个成员分配独立内存,按顺序偏移存储
成员偏移量 所有成员偏移量均为 0 成员偏移量需满足自身对齐值(可能有补空)
大小计算依据 最大成员大小 + 对齐补空(满足最大对齐值) 所有成员大小之和 + 偏移补空(满足最大对齐值)
数据访问特性 同一时刻只能有效访问一个成员 可同时访问所有成员,互不干扰

六、常见问题与注意事项

  1. “共用体大小 = 最大成员大小”一定成立吗?
    不一定。只有当最大成员大小是“最大对齐值”的整数倍时才成立(如实例 1);若最大成员大小不满足对齐值(如实例 2 中最大成员大小 5 < 对齐值 4 的整数倍 8),则需补空,此时共用体大小 > 最大成员大小。

  2. 不同编译器的对齐规则是否一致?
    基本规则一致,但默认对齐值可通过编译器指令修改(如 GCC 中的 __attribute__((aligned(n)))、MSVC 中的 #pragma pack(n))。例如,若设置 #pragma pack(2),则实例 2 中 union Test2 的最大对齐值变为 2,大小为 6(5 补空至 6,6 是 2 的整数倍)。

  3. 共用体成员的地址是否相同?
    是的。由于所有成员共享起始地址,&union_var.a == &union_var.b == &union_var(以实例 1 为例)。

七、练习题(附答案)

练习 1:计算以下共用体的大小

union Ex1 {
    short s;    // 大小 2,对齐值 2
    char c[3];  // 大小 3,对齐值 1
    long l;     // 大小 4,对齐值 4
};

答案:4 字节

解析:最大对齐值为 4,最大成员大小为 4,4 是 4 的整数倍,无需补空。

练习 2:计算以下共用体的大小(假设编译器默认对齐值为 8)

struct Inner {
    float f;  // 大小 4,对齐值 4
    char ch;  // 大小 1,对齐值 1
};

union Ex2 {
    struct Inner in;  // 结构体大小?对齐值?
    long long ll;     // 大小 8,对齐值 8
};

答案:8 字节

解析:

  1. 先算 struct Innerfloat f 占 0-3 字节,char ch 占 4 字节,大小 5;最大对齐值为 4,需补空至 8(4 的整数倍),因此结构体大小为 8,对齐值为 4。
  2. 共用体最大对齐值为 8(long long ll),最大成员大小为 8,满足对齐,最终大小 8。

0 条评论

目前还没有评论...