[C语言] 结构体 内存对齐规则 内存大小计算

目录

前言

01-基本概念

02-示例说明

[2.1 计算过程分析:](#2.1 计算过程分析:)

[2.2 输出结果:](#2.2 输出结果:)

注意:

03-示例2

[3.1 示例结构体定义](#3.1 示例结构体定义)

详细计算过程

[3.2 输出结果](#3.2 输出结果)

[3.3 关键结论](#3.3 关键结论)

04-示例3

[4.1 结构体大小计算过程](#4.1 结构体大小计算过程)

分步计算:

[4.2 输出结果:](#4.2 输出结果:)

[4.3 内存布局示意图:](#4.3 内存布局示意图:)

[4.4 总结:](#4.4 总结:)

[4.5 优化](#4.5 优化)

[优化后结构体的内存布局分析(地址从 0 开始):](#优化后结构体的内存布局分析(地址从 0 开始):)

05-优化核心思路:


C语言\] 结构体 内存对齐规则 内存大小计算 ## 前言 定义一个结构体,结构体中有 uint8 uint16 uint32的成员变量,怎么计算这个结构体在内存中的大小?请举个例子详细解释 ⭐在 C 语言中,结构体的内存大小计算需要考虑**成员变量的大小** 和**内存对齐(Padding)** 两个因素,而不仅仅是简单地将所有成员大小相加。内存对齐是编译器为了提高访问效率而采取的策略,不同成员类型有不同的对齐要求。 ## 01-基本概念 * 每个基本类型(如`uint8_t`、`uint16_t`、`uint32_t`)有其**自然对齐值** (通常等于其自身大小): * `uint8_t`(1 字节):对齐值为 1 * `uint16_t`(2 字节):对齐值为 2 * `uint32_t`(4 字节):对齐值为 4 * 结构体的总大小必须是其**最大成员对齐值**的整数倍。⭐ ## 02-示例说明 定义一个包含`uint8_t`、`uint16_t`、`uint32_t`的结构体,计算其内存大小: #include #include // 包含uint8_t、uint16_t、uint32_t的定义 // 定义结构体 struct MyStruct { uint8_t a; // 1字节 uint16_t b; // 2字节 uint32_t c; // 4字节 }; int main() { // 打印各成员大小 printf("uint8_t 大小: %zu 字节\n", sizeof(uint8_t)); printf("uint16_t 大小: %zu 字节\n", sizeof(uint16_t)); printf("uint32_t 大小: %zu 字节\n", sizeof(uint32_t)); // 打印结构体总大小 printf("结构体 MyStruct 大小: %zu 字节\n", sizeof(struct MyStruct)); return 0; } ### 2.1 计算过程分析: 1. **成员`a`(`uint8_t`)**: * 占 1 字节,从地址 0 开始存放(满足 1 字节对齐)。 * 此时已用内存:1 字节。 2. **成员`b`(`uint16_t`)**: * 要求 2 字节对齐(地址必须是 2 的倍数)。 * 上一个成员结束于地址 1,不满足对齐要求,因此需要**填充 1 字节的空白**(地址 1 处)。 * `b`从地址 2 开始存放,占用地址 2-3(2 字节)。 * 此时已用内存:1(a) + 1(填充) + 2(b) = 4 字节。 3. **成员`c`(`uint32_t`)**: * 要求 4 字节对齐(地址必须是 4 的倍数)。 * 上一个成员结束于地址 3,下一个地址 4 正好满足 4 字节对齐。 * `c`从地址 4 开始存放,占用地址 4-7(4 字节)。 * 此时已用内存:4 + 4(c) = 8 字节。 4. **结构体总大小**: * 最大成员对齐值为 4(`uint32_t`),8 是 4 的整数倍,无需额外填充。 * 最终大小:8 字节。 ### 2.2 输出结果: uint8_t 大小: 1 字节 uint16_t 大小: 2 字节 uint32_t 大小: 4 字节 结构体 MyStruct 大小: 8 字节 #### 注意: * 如果调整成员顺序,结构体大小可能改变。例如,将`uint32_t`放在最前: struct MyStruct { uint32_t c; // 4字节 uint16_t b; // 2字节 uint8_t a; // 1字节 }; * 此时总大小为 7 字节,但因需要满足 4 字节对齐,最终会填充 1 字节,总大小为 8 字节。 * 可以通过**#pragma pack**等编译器指令修改对齐方式,但不建议随意修改,可能影响性能。 从输出结果可以看到,这个结构体的总大小是 8 字节,而不是简单的 1+2+4=7 字节,这正是因为内存对齐机制导致的(在`uint8_t a`和`uint16_t b`之间填充了 1 字节的空白)。 我们可以通过打印成员的内存地址来更直观地看到对齐情况: #include #include struct MyStruct { uint8_t a; uint16_t b; uint32_t c; }; int main() { struct MyStruct s; printf("a的地址: %p\n", &s.a); printf("b的地址: %p\n", &s.b); printf("c的地址: %p\n", &s.c); return 0; } 假设输出的地址是: a的地址: 0x7ffd6b7a4a30 b的地址: 0x7ffd6b7a4a32 // 与a相差2字节(中间有1字节填充) c的地址: 0x7ffd6b7a4a34 // 与b相差2字节(刚好放下b) 可以清晰地看到: * `a`占用 1 字节(0x30) * 地址 0x31 被填充(不存储有效数据) * `b`从地址 0x32 开始(满足 2 字节对齐),占用 0x32-0x33 * `c`从地址 0x34 开始(满足 4 字节对齐),占用 0x34-0x37 整个结构体从 0x30 到 0x37,共 8 字节,刚好是最大成员(4 字节)的 2 倍,满足结构体整体对齐要求。 ## 03-示例2 uint8_t 大小: 1 字节 uint16_t 大小: 2 字节 uint8_t 大小: 1 字节 uint16_t 大小: 2 字节 uint32_t 大小: 4 字节 我们来分析包含`uint8_t`、`uint16_t`、`uint8_t`、`uint16_t`、`uint32_t`这 5 个成员的结构体大小,关键依然是**内存对齐规则**。 ### 3.1 示例结构体定义 #include #include struct MyStruct { uint8_t a; // 1字节 uint16_t b; // 2字节 uint8_t c; // 1字节 uint16_t d; // 2字节 uint32_t e; // 4字节 }; int main() { printf("结构体大小: %zu 字节\n", sizeof(struct MyStruct)); return 0; } #### 详细计算过程 详细计算过程(内存地址从 0 开始) 1. **成员`a`(`uint8_t`)**: * 占 1 字节,地址 0(满足 1 字节对齐) * 已用内存:1 字节 2. **成员`b`(`uint16_t`)**: * 要求 2 字节对齐(地址需为 2 的倍数) * 上一个成员结束于地址 0,需填充 1 字节(地址 1) * `b`从地址 2 开始,占用地址 2-3(2 字节) * 已用内存:1 + 1(填充) + 2 = 4 字节 3. **成员`c`(`uint8_t`)**: * 占 1 字节,地址 4(满足 1 字节对齐) * 已用内存:4 + 1 = 5 字节 4. **成员`d`(`uint16_t`)**: * 要求 2 字节对齐(地址需为 2 的倍数) * 上一个成员结束于地址 4,下一个地址 5 不是 2 的倍数,需填充 1 字节(地址 5) * `d`从地址 6 开始,占用地址 6-7(2 字节) * 已用内存:5 + 1(填充) + 2 = 8 字节 5. **成员`e`(`uint32_t`)**: * 要求 4 字节对齐(地址需为 4 的倍数) * 上一个成员结束于地址 7,下一个地址 8 正好是 4 的倍数 * `e`从地址 8 开始,占用地址 8-11(4 字节) * 已用内存:8 + 4 = 12 字节 6. **结构体总大小**: * 最大成员对齐值为 4(`uint32_t`) * 12 是 4 的整数倍,无需额外填充 * 最终大小:**12 字节** ### 3.2 输出结果 结构体大小: 12 字节 ### 3.3 关键结论 * 总大小不是简单相加(1+2+1+2+4=10 字节),而是因对齐填充了 2 字节(地址 1 和地址 5) * 成员顺序会影响总大小,合理排列成员(如将相同对齐要求的成员放在一起)可减少填充 // 优化后(大小仍为12字节,此处巧合) struct MyStruct { uint16_t b; // 2字节 uint16_t d; // 2字节 uint8_t a; // 1字节 uint8_t c; // 1字节 uint32_t e; // 4字节 }; ## 04-示例3 struct MyStruct { uint16_t b; // 2字节 uint16_t d; // 2字节 uint8_t a; // 1字节 uint8_t c; // 1字节 uint32_t e; // 4字节 uint8_t f; }; 我们来分析这个包含`uint16_t`、`uint8_t`、`uint32_t`等成员的结构体大小,重点依然遵循内存对齐规则 \*\*。 ### 4.1 结构体大小计算过程 结构体大小计算过程(地址从 0 开始) #include #include struct MyStruct { uint16_t b; // 2字节 uint16_t d; // 2字节 uint8_t a; // 1字节 uint8_t c; // 1字节 uint32_t e; // 4字节 uint8_t f; // 1字节 }; int main() { printf("结构体 MyStruct 大小: %zu 字节\n", sizeof(struct MyStruct)); return 0; } #### 分步计算: 1. **成员`b`(`uint16_t`)**: * 占 2 字节,从地址 0 开始(满足 2 字节对齐) * 已用内存:2 字节 2. **成员`d`(`uint16_t`)**: * 占 2 字节,从地址 2 开始(满足 2 字节对齐) * 已用内存:2 + 2 = 4 字节 3. **成员`a`(`uint8_t`)**: * 占 1 字节,从地址 4 开始(满足 1 字节对齐) * 已用内存:4 + 1 = 5 字节 4. **成员`c`(`uint8_t`)**: * 占 1 字节,从地址 5 开始(满足 1 字节对齐) * 已用内存:5 + 1 = 6 字节 5. **成员`e`(`uint32_t`)**: * 要求 4 字节对齐(地址需为 4 的倍数) * 上一个成员结束于地址 5,下一个可用地址 6 不是 4 的倍数,需填充 2 字节(地址 6-7) * `e`从地址 8 开始,占用地址 8-11(4 字节) * 已用内存:6 + 2(填充) + 4 = 12 字节 6. **成员`f`(`uint8_t`)**: * 占 1 字节,从地址 12 开始(满足 1 字节对齐) * 已用内存:12 + 1 = 13 字节 7. **结构体总大小**: * 最大成员对齐值为 4(`uint32_t`) * 当前已用 13 字节,需要填充 3 字节使总大小为 4 的整数倍(13 + 3 = 16) * 最终大小:**16 字节** ### 4.2 输出结果: 结构体 MyStruct 大小: 16 字节 ### 4.3 内存布局示意图: | 地址范围 | 内容 | 说明 | |-------|---------|-----------------| | 0-1 | `b` | 2 字节,`uint16_t` | | 2-3 | `d` | 2 字节,`uint16_t` | | 4 | `a` | 1 字节,`uint8_t` | | 5 | `c` | 1 字节,`uint8_t` | | 6-7 | 填充(无数据) | 为`e`对齐到 4 字节 | | 8-11 | `e` | 4 字节,`uint32_t` | | 12 | `f` | 1 字节,`uint8_t` | | 13-15 | 填充(无数据) | 使总大小为 4 的整数倍 | ### 4.4 总结: * 成员总大小为:2+2+1+1+4+1 = 11 字节 * 因对齐填充了 5 字节(6-7 地址 2 字节,13-15 地址 3 字节) * 最终结构体大小为 16 字节(满足最大对齐值 4 的整数倍要求) ### 4.5 优化 优化结构体成员的排布可以减少内存对齐带来的填充字节,从而减小结构体总大小。核心原则是:**将对齐要求相同或相近的成员放在一起,并且按对齐值从大到小(或从小到大)排列**。 针对你的结构体,我们可以这样优化: #include #include // 优化前的结构体(大小16字节) struct MyStructOriginal { uint16_t b; // 2字节 uint16_t d; // 2字节 uint8_t a; // 1字节 uint8_t c; // 1字节 uint32_t e; // 4字节 uint8_t f; // 1字节 }; // 优化后的结构体(大小12字节) struct MyStructOptimized { uint32_t e; // 4字节(最高对齐要求,放最前) uint16_t b; // 2字节 uint16_t d; // 2字节(与b对齐要求相同,放一起) uint8_t a; // 1字节 uint8_t c; // 1字节(与a对齐要求相同,放一起) uint8_t f; // 1字节(与a、c对齐要求相同,放一起) }; int main() { printf("优化前大小: %zu 字节\n", sizeof(struct MyStructOriginal)); printf("优化后大小: %zu 字节\n", sizeof(struct MyStructOptimized)); return 0; } #### 优化后结构体的内存布局分析(地址从 0 开始): 1. **成员`e`(`uint32_t`)**: * 占 4 字节,从地址 0 开始(满足 4 字节对齐) * 已用内存:4 字节 2. **成员`b`(`uint16_t`)**: * 占 2 字节,从地址 4 开始(满足 2 字节对齐) * 已用内存:4 + 2 = 6 字节 3. **成员`d`(`uint16_t`)**: * 占 2 字节,从地址 6 开始(满足 2 字节对齐) * 已用内存:6 + 2 = 8 字节 4. **成员`a`(`uint8_t`)**: * 占 1 字节,从地址 8 开始(满足 1 字节对齐) * 已用内存:8 + 1 = 9 字节 5. **成员`c`(`uint8_t`)**: * 占 1 字节,从地址 9 开始(满足 1 字节对齐) * 已用内存:9 + 1 = 10 字节 6. **成员`f`(`uint8_t`)**: * 占 1 字节,从地址 10 开始(满足 1 字节对齐) * 已用内存:10 + 1 = 11 字节 7. **结构体总大小**: * 最大成员对齐值为 4(`uint32_t`) * 当前已用 11 字节,需填充 1 字节使总大小为 4 的整数倍(11 + 1 = 12) * 最终大小:**12 字节**(比优化前减少 4 字节) ## 05-优化核心思路: 1. **优先放置高对齐要求的成员** :将`uint32_t e`(4 字节对齐)放在最前面,避免后续成员为了对齐它而产生大量填充。 2. **同类对齐成员集中放置** :将`uint16_t`类型的`b`和`d`放在一起,`uint8_t`类型的`a`、`c`、`f`放在一起,减少跨类型对齐产生的填充。 3. **最小化填充字节**:优化后仅在结构体末尾需要 1 字节填充(而优化前需要 5 字节填充)。 通过合理调整成员顺序,在不改变功能的前提下,有效减少了结构体的内存占用。

相关推荐
CHANG_THE_WORLD4 小时前
C++ 并发编程指南 实现无锁队列
开发语言·c++·缓存·无锁队列·无锁编程
CHANG_THE_WORLD4 小时前
C++ 内存模型:用生活中的例子理解并发编程
开发语言·c++·生活
闯闯桑4 小时前
toDF(columns: _*) 语法
开发语言·前端·spark·scala·apache
滴滴滴嘟嘟嘟.4 小时前
Qt图表功能学习
开发语言·qt·学习
charlie1145141914 小时前
Android开发——初步了解AndroidManifest.xml
android·xml·开发语言·学习·安卓·安全架构
Edward.W5 小时前
用 Go + HTML 实现 OpenHarmony 投屏(hdckit-go + WebSocket + Canvas 实战)
开发语言·后端·golang
努力努力再努力wz5 小时前
【c++进阶系列】:万字详解AVL树(附源码实现)
java·运维·开发语言·c++·redis
CHANG_THE_WORLD5 小时前
C++并发编程指南 std::promise 介绍与使用
java·开发语言·c++·promise
egoist20235 小时前
[linux仓库]性能加速的隐形引擎:深度解析Linux文件IO中的缓冲区奥秘
linux·运维·开发语言·缓存·缓冲区