欢迎光临
我们一直在努力

22、C语言程序设计:C语言结构体内存计算

C语言结构体内存计算(详细过程+5个通俗实例)

一、结构体内存计算的核心原理与详细过程

C语言中,结构体的内存大小并非其所有成员变量内存大小的简单相加,核心原因是内存对齐——编译器为了提高CPU访问内存的效率,会按照一定规则将结构体成员分配到特定的内存地址上,导致结构体总大小大于等于所有成员大小之和。

要掌握结构体内存计算,需先明确2个核心概念,再遵循3步计算流程,最后结合特殊情况修正,全程配合实例拆解,确保易懂。

(一)核心概念(必懂,否则无法计算)

  • 成员对齐数:每个结构体成员都有自己的对齐数,规则如下(通俗好记):

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

  • 自定义类型(结构体、联合体):对齐数 = 其内部最大成员的对齐数;

  • 编译器默认对齐数(可修改):多数编译器默认4(32位)或8(64位),成员最终对齐数 = 取“自身对齐数”和“编译器默认对齐数”的最小值(min)。

  • 结构体总对齐数:结构体的总大小,必须是其“所有成员中最大对齐数”的整数倍;若计算后总长度不足,需补空字节(padding,占位用,不存储数据)补齐。

  • (二)详细计算步骤(通用,适用于所有结构体)

    以“编译器默认对齐数4”为例(适配大多数初学者使用的编译器,如Dev-C++、VS默认32位模式),计算步骤固定3步,每一步都有明确目的:

  • 步骤1:确定每个成员的“最终对齐数”

  • 步骤2:按成员声明顺序,分配内存并计算偏移量

  • 步骤3:补齐结构体总长度

  • (三)5个通俗易懂的计算实例(覆盖所有常见场景,全程拆解)

    每个实例均遵循“程序段→计算步骤→运行验证”的逻辑,步骤拆解到每一个字节,确保初学者能看懂、能复现,所有实例均使用“默认对齐数4”。

    实例1:基础类型成员(无嵌套、无特殊类型)

    程序段(最基础,入门必看):

    #include <stdio.h>

    // 结构体:char + int + short(基础类型组合)
    struct Test1 {
        char a;    // 成员1:char,自身大小1
        int b;     // 成员2:int,自身大小4
        short c;   // 成员3:short,自身大小2
    };

    int main() {
        // 打印结构体大小,验证计算结果
        printf("结构体Test1的大小:%d字节\\n", sizeof(struct Test1));
        return 0;
    }

    计算步骤拆解(全程跟踪偏移量和已用内存):

  • 步骤1:确定每个成员的最终对齐数(默认对齐数4)

  • char a:min(1,4) = 1;

  • int b:min(4,4) = 4;

  • short c:min(2,4) = 2;

  • 最大对齐数:4(成员b的对齐数)。

  • 步骤2:按顺序分配内存,计算偏移量

  • 分配a(char):偏移量从0开始,0是1的整数倍,直接分配;占用地址0,已用内存1字节;

  • 分配b(int):下一个可用偏移量是1,1不是4的整数倍,补3个padding(地址1-3),偏移量变为4;4是4的整数倍,分配b,占用地址4-7,已用内存1+3+4=8字节;

  • 分配c(short):下一个可用偏移量是8,8是2的整数倍,直接分配;占用地址8-9,已用内存8+2=10字节。

  • 步骤3:补齐总长度 当前已用10字节,最大对齐数是4;10不是4的整数倍(4×2=8,4×3=12),补2个padding(地址10-11),总长度变为12字节。

  • 运行结果:结构体Test1的大小:12字节(与计算结果一致)。

    实例2:成员顺序不同,内存大小不同(关键考点)

    程序段(对比实例1,仅调整成员顺序):

    #include <stdio.h>

    // 结构体:int + char + short(仅调整成员顺序)
    struct Test2 {
        int b;     // 成员1:int,自身大小4
        char a;    // 成员2:char,自身大小1
        short c;   // 成员3:short,自身大小2
    };

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

    计算步骤拆解:

  • 步骤1:确定最终对齐数(与实例1一致)

  • int b:对齐数4;char a:对齐数1;short c:对齐数2;最大对齐数4。

  • 步骤2:分配内存

  • 分配b(int):偏移量0,0是4的整数倍,分配地址0-3,已用内存4字节;

  • 分配a(char):偏移量4,4是1的整数倍,分配地址4,已用内存4+1=5字节;

  • 分配c(short):下一个可用偏移量5,5不是2的整数倍,补1个padding(地址5),偏移量变为6;6是2的整数倍,分配c,占用地址6-7,已用内存5+1+2=8字节。

  • 步骤3:补齐总长度 已用8字节,是最大对齐数4的整数倍(4×2=8),无需补padding,总长度=8字节。

  • 运行结果:结构体Test2的大小:8字节(重点:成员顺序影响内存大小,合理排序可节省内存)。

    实例3:嵌套结构体(高频场景)

    程序段(结构体嵌套,模拟复杂对象):

    #include <stdio.h>

    // 嵌套的子结构体(家庭信息)
    struct Family {
        char address[5];  // char[5],自身大小5,对齐数1
        int zip;          // int,自身大小4,对齐数4
    };

    // 主结构体(学生,嵌套Family)
    struct Test3 {
        char name[2];     // char[2],对齐数1
        struct Family fam;// 嵌套结构体,对齐数=自身最大成员对齐数(4)
        short age;        // short,对齐数2
    };

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

     

    计算步骤拆解(先算嵌套子结构体,再算主结构体):

  • 第一步:计算嵌套子结构体Family的大小

  • 成员address[5]:对齐数1,偏移量0,分配地址0-4,已用5字节;

  • 成员zip:对齐数4,下一个偏移量5,5不是4的整数倍,补3个padding(地址5-7),偏移量8,分配zip(地址8-11),已用5+3+4=12字节;

  • Family最大对齐数4,12是4的整数倍,因此Family大小=12字节,对齐数=4。

  • 第二步:计算主结构体Test3的大小

  • 步骤1:确定主结构体成员对齐数 name[2]:对齐数1;fam:对齐数4;age:对齐数2;最大对齐数4。

  • 步骤2:分配内存 分配name[2]:偏移量0,分配地址0-1,已用2字节;分配fam:对齐数4,下一个偏移量2,2不是4的整数倍,补2个padding(地址2-3),偏移量4,分配fam(地址4-15),已用2+2+12=16字节;分配age:对齐数2,下一个偏移量16,16是2的整数倍,分配地址16-17,已用16+2=18字节。

  • 步骤3:补齐总长度 18不是4的整数倍(4×4=16,4×5=20),补2个padding(地址18-19),总长度=20字节。

  • 运行结果:结构体Test3的大小:20字节(嵌套结构体计算核心:先算子结构体,再作为一个成员参与主结构体计算)。

    实例4:修改编译器默认对齐数(取消对齐)

    程序段(用#pragma pack修改对齐数,实战常用):

    #include <stdio.h>

    // 修改编译器默认对齐数为1(取消对齐,按1字节分配)
    #pragma pack(1)
    struct Test4 {
        char a;    // 对齐数1
        int b;     // 对齐数1(min(4,1)=1)
        short c;   // 对齐数1(min(2,1)=1)
    };
    // 恢复默认对齐数(避免影响后续代码)
    #pragma pack()

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

      

    计算步骤拆解(对齐数改为1,无需补padding):

  • 步骤1:确定最终对齐数(默认对齐数1)

  • 所有成员的最终对齐数都是1(min(自身大小,1)=1),最大对齐数=1。

  • 步骤2:分配内存(无需补padding,直接顺序分配)

  • a:地址0,占用1字节,已用1;

  • b:地址1,占用4字节,已用1+4=5;

  • c:地址5,占用2字节,已用5+2=7。

  • 步骤3:补齐总长度(最大对齐数1,任何整数都是1的倍数,无需补padding) 总长度=7字节(等于所有成员大小之和:1+4+2=7)。

  • 运行结果:结构体Test4的大小:7字节(重点:#pragma pack(1)取消对齐,结构体大小=成员大小之和)。

    实例5:空结构体与含数组成员(特殊场景)

    程序段(覆盖空结构体、数组成员两个特殊情况):

    #include <stdio.h>

    // 空结构体(无任何成员)
    struct Empty {
        // 无成员
    };

    // 含数组成员的结构体
    struct Test5 {
        struct Empty e;  // 空结构体成员
        char arr[3];     // char数组,大小3,对齐数1
        int num;         // int,对齐数4
    };

    int main() {
        printf("空结构体Empty的大小:%d字节\\n", sizeof(struct Empty));
        printf("结构体Test5的大小:%d字节\\n", sizeof(struct Test5));
        return 0;
    }

     

    计算步骤拆解(特殊情况重点说明):

  • 第一步:计算空结构体Empty的大小 C语言标准规定:空结构体(无成员)的大小为1字节(编译器分配1个占位字节,避免多个空结构体地址重复),因此sizeof(struct Empty)=1字节,对齐数=1(自身最大成员无,默认1)。

  • 第二步:计算结构体Test5的大小

  • 步骤1:确定成员对齐数 e(Empty):对齐数1;arr[3]:对齐数1;num:对齐数4;最大对齐数4。

  • 步骤2:分配内存 分配e:偏移量0,占用1字节(地址0),已用1;分配arr[3]:偏移量1,1是1的整数倍,占用地址1-3,已用1+3=4;分配num:对齐数4,偏移量4是4的整数倍,占用地址4-7,已用4+4=8。

  • 步骤3:补齐总长度 8是最大对齐数4的整数倍,无需补padding,总长度=8字节。

  • 运行结果:

    空结构体Empty的大小:1字节 结构体Test5的大小:8字节(符合计算结果)。

    二、计算总结(必记重点)

  • 核心原则:内存对齐是为了提高CPU访问效率,代价是浪费少量padding字节;

  • 计算关键:3步走(确定对齐数→分配内存算偏移量→补齐总长度),嵌套结构体先算子结构体;

  • 易错点:成员顺序影响内存大小、空结构体大小为1、#pragma pack可修改对齐数;

  • 验证方法:所有实例均可直接运行,用sizeof打印结果,对比计算值,快速验证对错。

  • 赞(0)
    未经允许不得转载:171主机测评 » 22、C语言程序设计:C语言结构体内存计算
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址