欢迎光临
我们一直在努力

C 语言进阶:结构体从入门到精通 —— 定义、初始化与内存对齐

在 C 语言的编程世界里,基础数据类型(int、char、float等)只能描述单一属性,而现实中的复杂对象(如学生、书籍、员工)往往包含多个不同类型的属性。为了解决这个问题,C 语言提供了结构体(struct) 这一自定义类型,它是 C 语言实现复杂数据管理的核心工具。

本文将带你系统学习结构体的类型定义、变量创建、初始化,并重点图解结构体的内存对齐规则,彻底搞懂结构体的内存占用问题。

一、结构体基础认知

1. 什么是结构体?

结构体是用户自定义的聚合数据类型,可以将多个不同类型的变量封装在一起,用来描述一个对象的多个属性。 比如描述一个学生:包含姓名(字符数组)、年龄(整型)、学号(字符数组)、成绩(浮点型),用结构体就能完美整合这些属性。

2. 结构体类型的定义

定义结构体的语法格式:

// 1. 标准定义方式
struct 结构体名
{
成员1类型 成员1名称;
成员2类型 成员2名称;
// … 任意多个成员
};

// 2. 简化定义(定义时直接创建变量)
struct 结构体名
{
成员列表;
} 变量名1, 变量名2;

实战示例:定义学生结构体

// 定义一个描述学生的结构体类型
struct Student
{
// 成员变量
char name[20]; // 姓名
int age; // 年龄
char id[15]; // 学号
float score; // 成绩
};

⚠️ 注意:定义结构体类型时,不会分配内存,只有创建结构体变量时才会分配内存。


二、结构体变量的创建与初始化

1. 创建结构体变量

有了结构体类型,就可以像使用int、char一样创建变量:

// 方法1:先定义类型,再创建变量
struct Student stu1;

// 方法2:定义类型的同时创建变量
struct Student
{
char name[20];
int age;
} stu2, stu3;

2. 结构体变量的初始化

初始化就是给结构体的成员变量赋初值,有两种常用方式:

(1)按顺序初始化

// 定义+初始化(顺序必须和成员定义一致)
struct Student stu = {"张三", 18, "2025001", 95.5};

(2)指定成员初始化(推荐,更灵活)

C99 标准支持,不用严格按照顺序,可读性更强:

struct Student stu = {
.name = "李四",
.age = 19,
.score = 88.0,
.id = "2025002"
};

3. 结构体成员的访问

访问结构体成员使用点操作符(.),如果是结构体指针,使用箭头操作符(->):

#include <stdio.h>
struct Student
{
char name[20];
int age;
};

int main()
{
struct Student stu = {"王五", 20};
// 访问并打印成员
printf("姓名:%s\\n", stu.name);
printf("年龄:%d\\n", stu.age);

// 结构体指针访问
struct Student* p = &stu;
printf("年龄:%d\\n", p->age);
return 0;
}


三、重点:结构体的内存对齐(图解详解)

这是结构体最核心、面试最常考的知识点! 我们先看一个现象:

struct S
{
char c; // 1字节
int i; // 4字节
};

int main()
{
// 你以为输出5?实际输出8!
printf("%zu\\n", sizeof(struct S));
return 0;
}

为什么1+4=5,最终大小却是8?这就是内存对齐在起作用。

1. 为什么需要内存对齐?

  • 硬件平台限制:部分硬件只能从特定地址读取数据,未对齐的数据会读取失败;
  • 提升访问效率:CPU 读取对齐的数据,只需要一次访问;未对齐的数据需要多次读取拼接,效率极低。
  • 总结:空间换时间,是编译器的默认优化规则。

    2. 内存对齐的核心规则

    记住 3 条黄金规则,所有结构体内存计算都能搞定:

  • 第一个成员:永远存储在结构体偏移量为 0的地址处;
  • 后续成员:存储在该成员大小的整数倍的偏移地址处;
  • 结构体总大小:必须是最大成员大小的整数倍;
  • 嵌套结构体:嵌套的结构体对齐到自己最大成员的整数倍,结构体总大小是所有最大成员的整数倍。
  • 3. 图解内存对齐(实战案例)

    案例 1:基础结构体

    struct S
    {
    char c; // 1字节
    int i; // 4字节
    };

    内存布局图解:

    偏移量:0 1 2 3 4 5 6 7
    ┌──┬────────┬────────────┐
    │c │ 填充 │ i │
    └──┴────────┴────────────┘

    • 规则 1:char c 放在偏移量 0 处,占用 1 字节;
    • 规则 2:int i 占 4 字节,必须放在 4 的整数倍地址,因此偏移量 0~3 中,偏移量 1~3 填充 3 个字节;
    • 规则 3:总大小 = 1 (填充) + 4 = 8,是最大成员int(4)的整数倍,符合要求。

    最终大小:8 字节。


    案例 2:调整成员顺序(大小会变!)

    struct S2
    {
    char c1; // 1
    char c2; // 1
    int i; // 4
    };

    内存布局图解:

    偏移量:0 1 2 3 4 5 6 7
    ┌──┬──┬────────┬────────────┐
    │c1│c2│ 填充 │ i │
    └──┴──┴────────┴────────────┘

    • c1放 0,c2放 1(1 的整数倍);
    • int i需要放 4 的整数倍,偏移量 2~3 填充;
    • 总大小:8 字节。

    案例 3:嵌套结构体

    struct A
    {
    char a;
    int b;
    };

    struct B
    {
    char c;
    struct A s;
    double d;
    };

    计算步骤:

  • struct A大小:8 字节(最大成员 4);
  • struct B中:
    • char c放 0;
    • 嵌套struct A对齐到 4 的整数倍,偏移量 4 开始;
    • double d对齐到 8 的整数倍;
  • 总大小:24 字节(最大成员 8,24 是 8 的整数倍)。
  • 4. 修改默认对齐数

    编译器默认对齐数通常是 4/8,我们可以用#pragma pack(n)手动修改:

    // 设置默认对齐数为2
    #pragma pack(2)
    struct S
    {
    char c;
    int i;
    };
    // 恢复默认对齐
    #pragma pack()

    此时结构体大小变为6 字节。


    四、补充:位段、共用体与结构体的区别

    你代码中出现的位段、共用体(union) 是结构体的延伸,这里做简单区分:

  • 位段:在结构体基础上,用:指定成员占用二进制位数,用来节省内存; ⚠️ 注意:位段成员不能取地址(&),这也是你代码中scanf("%d", &sa._b);报错的原因。
  • 共用体:所有成员共用同一块内存,同一时间只能存储一个成员,大小为最大成员的大小。

  • 五、总结

  • 结构体是聚合数据类型,用于封装多个不同类型的成员;
  • 定义类型不分配内存,创建变量才分配内存,支持顺序 / 指定成员初始化;
  • 访问成员:普通变量用.,指针用->;
  • 内存对齐是核心:遵循「首成员偏移 0、后续对齐整数倍、总大小是最大成员整数倍」的规则;
  • 内存对齐的本质:空间换时间,提升 CPU 访问效率。
  • 掌握结构体的定义、初始化和内存对齐,是编写高效 C 语言程序、应对面试的必备技能!建议大家手动计算几个结构体的内存大小,结合图解加深理解。

    赞(0)
    未经允许不得转载:171主机测评 » C 语言进阶:结构体从入门到精通 —— 定义、初始化与内存对齐
    分享到: 更多 (0)

    评论 抢沙发

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