一、共用体内存计算的核心原理(先懂本质,再算大小)

共用体(Union)是C语言中一种特殊的自定义数据类型,其内存分配的核心逻辑与结构体(Struct)完全不同——结构体成员“独享内存、顺序分配”,共用体所有成员“共享同一块内存空间”,同一时间只能使用一个成员(修改一个成员会覆盖其他成员的值)。

共用体内存计算无需像结构体那样逐成员分配、逐成员对齐,核心是“容纳最大成员+整体对齐”,掌握2个核心规则和2步计算流程,就能轻松算出所有共用体的内存大小。

(一)核心概念(必记,避免计算出错)

  1. 成员对齐数:与结构体完全一致,无需重新记忆:

    1. 基本数据类型(char、short、int、float、double等):对齐数 = 自身大小(如char=1、int=4、double=8);

    2. 自定义类型(共用体、结构体):对齐数 = 其内部最大成员的对齐数;

    3. 编译器默认对齐数(可通过#pragma pack(n)修改,n为1、2、4、8等):成员最终对齐数 = min(成员自身对齐数, 编译器默认对齐数)。

  2. 共用体总对齐数:共用体的总大小,必须是其“所有成员中最大对齐数”的整数倍(与结构体的总对齐规则一致)。

(二)共用体内存计算的详细步骤(通用流程,适用于所有场景)

以“编译器默认对齐数4”为例(适配32位编译器,如Dev-C++、VS默认32位模式,初学者最常用),计算步骤仅需2步,简洁易懂:

  1. 步骤1:确定“最大成员大小”——共用体的最小内存空间,必须能容纳其内部最大的成员(否则最大的成员无法存储),记为max_size;

  2. 步骤2:补齐对齐——将max_size补齐为“所有成员中最大对齐数”(记为max_align)的整数倍,补齐后的长度即为共用体的最终内存大小(sizeof(union)的结果)。

简单总结:共用体大小 = 能容纳最大成员的最小空间 + 对齐所需的空字节(padding),核心是“共享内存,只留最大成员的空间,再对齐”。

二、5个通俗易懂的共用体内存计算实例(覆盖所有常见场景)

每个实例均遵循“程序段→计算步骤→运行验证→核心解读”的逻辑,步骤拆解到字节,程序可直接复制运行,验证计算结果,确保初学者能看懂、能复现、能举一反三。

实例1:基础类型成员(最简场景,无对齐补齐)

程序段(入门必看,理解共享内存本质)

#include <stdio.h>

// 共用体:char + int + short(基础类型组合)
union Test1 {
    char a;    // 大小1,对齐数1(min(1,4)=1)
    int b;     // 大小4,对齐数4(min(4,4)=4)
    short c;   // 大小2,对齐数2(min(2,4)=2)
};

int main() {
    // 打印共用体大小,验证计算结果
    printf("共用体Test1的大小:%d字节\n", sizeof(union Test1));
    
    // 验证成员共享内存(所有成员起始地址完全相同)
    union Test1 u;
    printf("成员a的地址:%p\n", &u.a);
    printf("成员b的地址:%p\n", &u.b);
    printf("成员c的地址:%p\n", &u.c);
    
    return 0;
}

计算步骤(全程拆解)

  1. 步骤1:找最大成员大小(max_size)——成员b是int类型,大小4,是三个成员中最大的,因此max_size=4;

  2. 步骤2:找最大对齐数(max_align)——三个成员的对齐数分别是1、4、2,最大对齐数是4;

  3. 步骤3:补齐对齐——max_size=4,是max_align=4的整数倍(4×1=4),无需补padding;

  4. 最终大小:4字节。

运行结果

共用体Test1的大小:4字节 成员a的地址:0061FEAC 成员b的地址:0061FEAC 成员c的地址:0061FEAC

核心解读

所有成员共享同一块4字节内存,这是共用体的核心特性:比如给u.b赋值0x12345678,再打印u.a会得到0x78(int的低1字节),打印u.c会得到0x5678(int的低2字节),体现“覆盖使用”的特点;总大小仅需容纳最大的int成员,且刚好满足对齐要求。

实例2:最大成员大小需补齐对齐(核心考点)

程序段(对齐补齐的典型场景)

#include <stdio.h>

// 共用体:char[5] + short[3](数组成员,考验对齐补齐)
union Test2 {
    char arr1[5];  // 大小5(1×5),对齐数1
    short arr2[3]; // 大小6(2×3),对齐数2
};

int main() {
    printf("共用体Test2的大小:%d字节\n", sizeof(union Test2));
    return 0;
}

计算步骤

  1. 步骤1:找最大成员大小(max_size)——arr2是short[3],大小=2×3=6,比arr1[5](大小5)大,因此max_size=6;

  2. 步骤2:找最大对齐数(max_align)——arr1的对齐数1,arr2的对齐数2,最大对齐数是2;

  3. 步骤3:补齐对齐——max_size=6,是2的整数倍(2×3=6),无需补padding;

  4. 最终大小:6字节。

运行结果

共用体Test2的大小:6字节

核心解读

共用体的大小由“最大成员”决定,而非成员大小之和:虽然arr1[5]只需要5字节,但共用体必须容纳更大的arr2[3](6字节);6字节刚好是最大对齐数2的整数倍,因此无需补空字节,这是初学者容易混淆的点。

实例3:嵌套共用体(复杂场景,实战常用)

程序段(嵌套结构的计算逻辑)

#include <stdio.h>

// 嵌套的子共用体(内部包含float和long)
union SubUnion {
    float f;   // 大小4,对齐数4
    long l;    // 32位系统中大小4,对齐数4(64位系统long=8,对齐数4)
};

// 主共用体(嵌套子共用体,包含多种类型)
union Test3 {
    union SubUnion su; // 子共用体,作为一个成员
    char c;            // 大小1,对齐数1
    double d;          // 大小8,对齐数4(min(8,4)=4,默认对齐数4)
};

int main() {
    printf("子共用体SubUnion的大小:%d字节\n", sizeof(union SubUnion));
    printf("主共用体Test3的大小:%d字节\n", sizeof(union Test3));
    return 0;
}

计算步骤(先算子,再算主)

  1. 第一步:计算子共用体SubUnion

    1. 最大成员:float f和long l,大小均为4,max_size=4;

    2. 最大对齐数:4,4是4的整数倍,无需补齐;

    3. 子共用体大小:4字节。

  2. 第二步:计算主共用体Test3

    1. 最大成员:double d,大小8,max_size=8;

    2. 最大对齐数:double d的最终对齐数=min(8,4)=4,其他成员对齐数均小于4,因此max_align=4;

    3. 补齐对齐:8是4的整数倍(4×2=8),无需补padding;

    4. 主共用体大小:8字节。

运行结果

子共用体SubUnion的大小:4字节 主共用体Test3的大小:8字节

核心解读

嵌套共用体的计算逻辑:先计算子共用体的大小和对齐数,再将其作为“普通成员”参与主共用体的计算;这里double d的自身对齐数是8,但编译器默认对齐数为4,因此最终对齐数取4,8字节刚好满足对齐要求,无需额外补齐。

实例4:修改编译器默认对齐数(实战场景,灵活调整)

程序段(修改对齐数,验证对齐规则)

#include <stdio.h>

// 修改编译器默认对齐数为2(实战中常用,节省内存)
#pragma pack(2)
union Test4 {
    char a;    // 大小1,对齐数1(min(1,2)=1)
    int b;     // 大小4,对齐数2(min(4,2)=2)
    double c;  // 大小8,对齐数2(min(8,2)=2)
};
// 恢复默认对齐数(避免影响后续代码)
#pragma pack()

int main() {
    printf("共用体Test4的大小:%d字节\n", sizeof(union Test4));
    return 0;
}

计算步骤

  1. 步骤1:找最大成员大小(max_size)——double c,大小8,max_size=8;

  2. 步骤2:找最大对齐数(max_align)——默认对齐数改为2,所有成员的最终对齐数均不超过2,最大对齐数=2;

  3. 步骤3:补齐对齐——8是2的整数倍(2×4=8),无需补padding;

  4. 最终大小:8字节。

运行结果

共用体Test4的大小:8字节

核心解读

#pragma pack(n)可灵活修改编译器默认对齐数,n越小,对齐要求越宽松,可能节省内存;若将对齐数改为1(取消对齐),Test4的大小仍为8字节(最大成员是double c,8是1的整数倍),说明“最大成员大小”是共用体大小的基础,对齐仅影响是否补齐。

实例5:共用体vs结构体(对比场景,加深理解)

程序段(通过对比,彻底区分两者内存分配差异)

#include <stdio.h>

// 结构体(对比组:成员独享内存)
struct Stu {
    char a;    // 大小1,对齐数1
    int b;     // 大小4,对齐数4
    short c;   // 大小2,对齐数2
};

// 共用体(测试组:成员共享内存)
union Uni {
    char a;    // 大小1,对齐数1
    int b;     // 大小4,对齐数4
    short c;   // 大小2,对齐数2
};

int main() {
    printf("结构体Stu的大小:%d字节\n", sizeof(struct Stu));
    printf("共用体Uni的大小:%d字节\n", sizeof(union Uni));
    return 0;
}

计算步骤(对比拆解)

  1. 结构体Stu计算(回顾,对比共用体)

    1. 成员对齐数:a(1)、b(4)、c(2),最大对齐数4;

    2. 分配内存:a(0,1字节)→补3字节padding→b(4,4字节)→c(8,2字节),已用10字节;

    3. 补齐对齐:10补2字节→总大小12字节。

  2. 共用体Uni计算

    1. 最大成员:b(4字节),max_size=4;

    2. 最大对齐数:4,4是4的整数倍→总大小4字节。

运行结果

结构体Stu的大小:12字节 共用体Uni的大小:4字节

核心解读

这是最直观的对比:结构体是“累加+对齐”,每个成员独享内存,总大小是所有成员大小之和(加padding);共用体是“取最大+对齐”,所有成员共享内存,总大小仅需容纳最大成员,再补齐对齐,因此共用体更节省内存,但不能同时使用多个成员。

三、共用体内存计算总结(必记重点,避免踩坑)

  1. 核心逻辑:所有成员共享同一块内存,总大小由“最大成员大小”和“最大对齐数”决定,公式可简化为:共用体大小 = 对齐后的最大成员大小;

  2. 对齐规则:与结构体的“总对齐规则”完全一致,仅无需逐成员对齐,只需对最大成员的大小进行对齐补齐;

  3. 易错点:① 不要同时访问共用体的多个成员,其值会相互覆盖;② 嵌套共用体需先算子共用体的大小和对齐数;③ 空共用体(无成员)在多数编译器中大小为1字节(占位,避免地址重复);

  4. 验证方法:所有实例均可直接复制运行,用sizeof打印结果,对比计算值,快速验证对错,加深记忆。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐