C 语言结构体内存对齐:原来是这么回事!

开发
内存对齐看起来是个小细节,但它体现了计算机系统设计的精妙之处 —— 在效率和空间使用之间寻找平衡。

大家好,我是小康。

今天咱们聊一个看似复杂实则很有意思的话题 —— C 语言中的结构体内存对齐。别被这个名字吓到,我保证用最接地气的方式带你彻底搞懂它!

很多初学者学习 C 语言时都会遇到这样的困惑:为啥我定义的结构体占用的内存总是比我想象的大?明明加起来应该是这么多字节,实际却要更多?这就是内存对齐在捣鬼啦!

一、什么是内存对齐?先来个生活例子

想象一下,你去超市购物,收银台前排了一长队。超市为了提高效率,规定:

  • 购买 1-3 件商品的顾客,必须站在 3 的倍数位置(第 3、6、9... 个位置)
  • 购买 4-7 件商品的顾客,必须站在 4 的倍数位置(第 4、8、12... 个位置)
  • 购买 8 件以上商品的顾客,必须站在 8 的倍数位置(第 8、16、24... 个位置)

这样会怎样?队伍中肯定会出现空位!但收银员处理起来更有效率,因为他能快速判断每位顾客大概需要多长时间。

内存对齐就是这个道理。电脑处理不同大小的数据类型时,也喜欢把它们放在特定的"位置"上,这样处理起来更高效,即使这意味着有些内存看起来被"浪费"了。

二、为什么需要内存对齐?

简单说:为了提高访问效率。

现代计算机的 CPU 访问内存时,并不是一个字节一个字节地读取,而是一次读取固定大小的块(比如 4 字节或 8 字节)。如果你的数据刚好在这些块的边界上,那访问起来就很高效;如果数据跨越了边界,CPU 就需要多读几次,效率自然就低了。

就像你去图书馆借书,管理员一次能搬运 8 本书。如果你要的书刚好摆在 8 本一组的架子上,取起来就很方便;如果你的书跨了两组,管理员就得跑两趟,多费劲啊!

三、对齐规则:简单又有趣

C 语言的内存对齐遵循三个基本规则:

  • 每个成员相对于结构体起始位置的偏移量必须是自身大小的整数倍
  • 结构体的总大小必须是最大成员大小的整数倍
  • 结构体大小至少是所有成员大小之和,再加上为满足前两条规则所需的填充字节

这听起来有点复杂?别急,我画个图,保证你一看就懂!

四、来个直观的例子

假设我们有这样一个结构体:

struct Example {
    char a;     // 1字节
    int b;      // 4字节
    char c;     // 1字节
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

按理说,这个结构体应该占用 1 + 4 + 1 = 6 字节,对吧?但实际上它占用了 12 字节!为什么?

让我们用图来表示内存布局:

字节位置: 0  1  2  3  4  5  6  7  8  9  10 11
内存内容: a  -  -  -  b  b  b  b  c  -  -  -
            |-填充-|                 |-填充-|
  • 1.
  • 2.
  • 3.

解释一下:

  • a 占用第0个字节
  • 由于 b 是 int 类型(4字节),按对齐规则它的起始位置必须是 4 的整数倍,所以跳过 1-3 字节(填充3个字节),从第 4 个字节开始
  • b 占用第 4-7 字节
  • c 占用第 8 个字节
  • 最后,整个结构体的大小必须是其最大成员(这里是int,4字节)的整数倍,所以还要填充到 12 字节

五、调整顺序可以节省空间

聪明的你可能已经想到了:如果我们调整结构体成员的顺序,是不是就能减少这些"浪费"的填充字节呢?

没错!看这个例子:

struct BetterExample {
    int b;      // 4字节
    char a;     // 1字节
    char c;     // 1字节
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

现在的内存布局变成了:

字节位置: 0  1  2  3  4  5  6  7
内存内容: b  b  b  b  a  c  -  -
                           |填充|
  • 1.
  • 2.
  • 3.

通过简单地调整顺序,结构体大小从 12 字节减少到了 8 字节!是不是很神奇?

六、实战:验证我们的理解

来写个小程序验证一下(32位系统下):

#include <stdio.h>

struct Example1 {
    char a;     // 1字节
    int b;      // 4字节
    char c;     // 1字节
};

struct Example2 {
    int b;      // 4字节
    char a;     // 1字节
    char c;     // 1字节
};

int main() {
    printf("Example1大小: %lu字节\n", sizeof(struct Example1));
    printf("Example2大小: %lu字节\n", sizeof(struct Example2));
    return0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

运行结果:

Example1大小: 12字节
Example2大小: 8字节
  • 1.
  • 2.

看吧,和我们分析的完全一致!

七、如何手动控制对齐方式?

有时候,我们可能需要更精确地控制内存对齐,C语言提供了几种方法:

(1) 使用编译器指令:

#pragma pack(1)  // 设置按1字节对齐
struct CompactExample {
    char a;
    int b;
    char c;
};
#pragma pack()   // 恢复默认对齐
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

(2) 使用属性声明(GNU C):

struct CompactExample {
    char a;
    int b;
    char c;
} __attribute__((packed));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

这两种方法都能让我们的 CompactExample 结构体严格占用 6 字节,没有任何填充。但要注意,这样做可能会降低程序的运行效率,特别是在某些对内存对齐要求严格的 CPU 架构上。

八、实际应用:为什么要关心内存对齐?

嵌入式系统和内存受限场景:在资源紧张的环境中,合理安排结构体成员顺序可以节省大量内存。

  • 网络通信和文件IO:不同系统可能有不同的对齐方式,传输数据时需要考虑这一点。
  • 提高程序性能:了解内存对齐可以帮助你写出更高效的代码。

小结:看完是不是觉得相见恨晚?

内存对齐看起来是个小细节,但它体现了计算机系统设计的精妙之处 —— 在效率和空间使用之间寻找平衡。掌握了这个知识点,你就能:

  • 理解为什么有时候结构体大小和你预计的不一样
  • 通过合理安排成员顺序优化内存使用
  • 在需要时手动控制对齐方式
  • 写出更高效、更专业的代码

怎么样,是不是觉得这个知识点其实挺简单,又特别实用?希望这篇文章能帮你彻底搞懂 C 语言结构体内存对齐这个看似复杂的概念!

责任编辑:赵宁宁 来源: 跟着小康学编程
相关推荐

2020-06-30 08:12:32

VMwareKVMDocker

2022-08-15 08:01:00

三色标记JVM算法

2021-07-29 16:56:59

微信腾讯注册

2022-10-21 08:17:13

MongoDB查询Document

2018-06-04 08:40:20

磁盘分区MBR

2021-02-07 08:13:18

@DateTimeFo@NumberFormSpring

2020-02-23 15:55:00

疫情AI人工智能

2020-03-04 08:47:10

Kafka架构原理

2020-11-12 07:32:53

JavaScript

2021-02-11 09:14:36

内存虚拟机数据

2020-06-12 10:46:18

C语言栈内存结构体

2024-10-11 11:59:03

2022-01-14 14:19:38

ReactTS前端

2011-05-25 09:58:46

C#

2017-06-06 15:13:07

2012-01-11 09:15:45

Objective-C

2014-03-31 14:59:08

大数据

2023-04-09 23:25:30

Java注解元注解

2022-12-14 07:32:40

InnoDBMySQL引擎

2009-03-10 12:42:45

点赞
收藏

51CTO技术栈公众号