C语言struct内存对齐规则如何优化?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

struct 是 C 语言中一种非常重要的复合数据类型,它允许你将不同类型的数据项组合成一个单一的类型,你可以把它想象成一个“容器”或者一个“蓝图”,用来描述一个更复杂的数据结构。

c语言 struct
(图片来源网络,侵删)

为什么需要 struct

想象一下你要描述一个“学生”的信息,一个学生有:

  • 姓名(字符串)
  • 学号(整数)
  • 年龄(整数)
  • 成绩(浮点数)

如果用 C 语言的基本类型,你可能需要定义多个独立的变量:

char name[50];
int id;
int age;
float score;

这样做有几个问题:

  1. 数据不相关:这些变量在逻辑上是相关的,但在代码中是分散的。
  2. 参数传递麻烦:如果你想把一个学生的信息传递给一个函数,你需要传递所有这些变量,非常繁琐。
  3. 难以管理:当学生数量增多时,管理成百上千个独立的变量会是一场噩梦。

struct 就是为了解决这个问题而生的,它能把所有这些相关的数据项打包成一个名为 Student 的单一类型。

c语言 struct
(图片来源网络,侵删)

如何定义和使用 struct

1 定义结构体类型

定义一个结构体类型,需要使用 struct 关键字。

语法:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    // ...
    数据类型 成员N;
};

示例:定义一个 Student 结构体

// 定义一个名为 Student 的结构体类型
struct Student {
    char name[50];
    int id;
    int age;
    float score;
};

注意:这只是一个类型定义,像 int 一样,它本身不占用内存,它只是创建了一个“模板”或“蓝图”。

c语言 struct
(图片来源网络,侵删)

2 声明结构体变量

定义好类型后,你就可以像使用 int, char 一样来声明变量了。

先定义类型,再声明变量

struct Student { /* ... 成员 ... */ }; // 类型定义
// 声明两个 Student 类型的变量
struct Student student1;
struct Student student2;

在定义类型的同时声明变量

// 定义 Student 类型,并同时声明 s1 和 s2 两个变量
struct Student {
    char name[50];
    int id;
    int age;
    float score;
} s1, s2;

使用 typedef 创建别名(推荐)

为了在声明变量时省去 struct 关键字,通常会使用 typedef,这是一种非常普遍和推荐的用法。

// 定义 Student 结构体类型,并给它起一个别名叫 Student
typedef struct {
    char name[50];
    int id;
    int age;
    float score;
} Student;
// 现在可以直接用 Student 来声明变量,无需 struct
Student student1;
Student student2;

如何访问结构体成员?

要访问结构体变量中的成员,使用成员访问运算符,也就是点号 。

语法: 结构体变量名.成员名

示例:

#include <stdio.h>
#include <string.h>
// 使用 typedef 定义 Student
typedef struct {
    char name[50];
    int id;
    int age;
    float score;
} Student;
int main() {
    // 声明并初始化一个 Student 变量
    Student student1;
    strcpy(student1.name, "张三"); // 字符串不能直接用 = 赋值,需要 strcpy
    student1.id = 1001;
    student1.age = 20;
    student1.score = 95.5;
    // 声明另一个变量并逐个赋值
    Student student2;
    student2.age = 21;
    student2.score = 88.0;
    // ... 其他成员赋值
    // 访问并打印成员
    printf("学生姓名: %s\n", student1.name);
    printf("学生学号: %d\n", student1.id);
    printf("学生年龄: %d\n", student1.age);
    printf("学生成绩: %.2f\n", student1.score);
    return 0;
}

结构体的初始化

你可以在声明结构体变量的同时,使用花括号 来初始化它的成员。

示例:

Student student1 = {"李四", 1002, 19, 92.5};
// 如果只初始化部分成员,未初始化的成员会被自动设置为0(对于数值类型)或'\0'(对于字符)
Student student2 = {"王五", 1003}; // age 和 score 会被自动初始化为 0

结构体数组

你可以创建一个结构体数组,来存储多个相同类型的结构体对象。

示例:

Student class[30]; // 定义一个可以容纳30个学生的数组
// 访问数组中的元素
class[0].id = 1001;
strcpy(class[0].name, "张三");
class[1].id = 1002;
strcpy(class[1].name, "李四");

结构体指针

你也可以声明一个指向结构体的指针。

关键点:

  • 使用 ->(箭头运算符)来通过指针访问成员。
  • ->(*指针名).成员名 的简写形式。

示例:

Student student = {"赵六", 1004, 22, 99.0};
Student *ptr; // 声明一个结构体指针
ptr = &student; // 让指针指向 student 变量的地址
// 通过指针访问成员
printf("姓名: %s\n", ptr->name);  // 使用 -> 运算符
printf("学号: %d\n", ptr->id);
// 也可以使用解引用的方式
printf("成绩: %f\n", (*ptr).score);

结构体作为函数参数

结构体可以作为函数的参数传递,主要有两种方式:

a) 传值(Pass-by-Value)

将结构体的副本传递给函数,函数内部对副本的修改不会影响原始变量,这种方式简单,但当结构体很大时,会消耗较多内存和时间进行拷贝。

void printStudent(Student s) {
    printf("打印学生信息: %s, %d\n", s.name, s.id);
}
int main() {
    Student student = {"钱七", 1005, 23, 85.5};
    printStudent(student); // 传递副本
    return 0;
}

b) 传指针(Pass-by-Pointer)

传递结构体的地址,函数通过指针直接操作原始变量,效率更高,并且可以修改原始数据。

void updateScore(Student *s, float newScore) {
    s->score = newScore; // 通过指针修改原始数据
}
int main() {
    Student student = {"孙八", 1006, 24, 75.0};
    printf("修改前成绩: %.2f\n", student.score);
    updateScore(&student, 80.5); // 传递地址
    printf("修改后成绩: %.2f\n", student.score);
    return 0;
}

对于较大的结构体,推荐使用指针传递,以提高效率。


结构体的大小与内存对齐

sizeof 运算符可以获取结构体的大小。

printf("Student 结构体的大小是: %zu 字节\n", sizeof(Student));

你会发现,sizeof(Student) 的大小通常大于其所有成员大小之和(sizeof(name) + sizeof(id) + ...),这是因为编译器为了提高内存访问效率,会进行内存对齐

  • 什么是内存对齐? 编译器会在结构体成员之间填充一些字节,使得每个成员都从某个特定地址(通常是它自身大小的整数倍)开始存放。
  • 为什么要对齐? 现代CPU在访问内存时,如果地址是对齐的,速度会更快,unaligned access 可能会导致性能下降甚至程序崩溃。
  • 如何优化? 将小的数据类型放在一起,大的数据类型放在一起,可以减少填充字节,从而减小结构体总大小。

示例:

struct BadExample {
    char c;      // 1 byte
    int i;       // 4 bytes (在 c 后面填充了 3 bytes)
    double d;    // 8 bytes
};
struct GoodExample {
    double d;    // 8 bytes
    int i;       // 4 bytes (在 d 后面填充了 4 bytes)
    char c;      // 1 byte (在 i 后面填充了 3 bytes)
};
// BadExample 的大小可能是 1 + 3 + 4 + 4 + 8 = 20 字节 (取决于编译器)
// GoodExample 的大小可能是 8 + 4 + 4 + 1 + 3 = 20 字节 (取决于编译器)
// 这个例子对齐效果不明显,但原则是:把类型相近的放在一起。

结构体与 typedef 的常见写法

初学者可能会对 typedefstruct 的组合感到困惑,这里列出最常见的几种写法:

最清晰、推荐

先定义结构体,再用 typedef 起别名。

struct Point {
    int x;
    int y;
};
typedef struct Point Point; // 给 struct Point 起一个别名 Point

或者更简洁的写法(省略 Point):

struct Point {
    int x;
    int y;
};
typedef struct Point Point; // 等同于 typedef struct Point Point;

优点:代码清晰,可读性高,struct PointPoint 都可以使用。

匿名结构体 + typedef (最常用)

直接在 typedef 中定义结构体。

typedef struct {
    int x;
    int y;
} Point;

优点:简洁,一行代码搞定,这是在C语言项目中最常见的写法,缺点是,如果想在别处使用 struct Point 这个完整的类型名(例如在函数参数中明确表示是结构体类型),则不行。

不推荐(易混淆)

typedef struct Point {
    int x;
    int y;
} Point;

这种写法功能上和写法二一样,但 Point 这个名字在 struct 内部和外部都存在,可能会让一些初学者混淆,写法二(匿名结构体)更清晰,表明 Point 这个名字是 typedef 给整个结构体的别名,而不是结构体内部的某个东西。


特性 描述
定义 struct 是一种复合数据类型,用于组合不同类型的数据。
声明 struct Student s1; 或使用 typedefStudent s1;
访问 使用点号 访问变量成员,使用箭头 -> 访问指针成员。
初始化 在声明时使用 初始化。
数组 可以创建结构体数组 struct Student class[100];
指针 指向结构体的指针用于高效地传递和操作结构体数据。
参数 可以传值(副本)或传指针(地址),后者更高效。
内存 存在内存对齐,sizeof 结果可能大于成员大小之和。
typedef 强烈推荐使用 typedef 来简化结构体类型的声明。

struct 是 C 语言构建复杂数据结构(如链表、树、图等)的基础,也是面向对象编程思想在 C 语言中的雏形(C++ 中的 class 就是在 struct 的基础上扩展而来的),掌握 struct 是从 C 语言新手进阶到熟练的关键一步。

-- 展开阅读全文 --
头像
dede自定义时间格式如何设置?
« 上一篇 03-02
织梦的产品加价格属性
下一篇 » 03-02

相关文章

取消
微信二维码
支付宝二维码

目录[+]