当前位置: 首页 > news >正文

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

目录

前言

01-基本概念

02-示例说明

2.1 计算过程分析:

2.2 输出结果:

注意:

03-示例2

3.1 示例结构体定义

详细计算过程

3.2 输出结果

3.3 关键结论

04-示例3

4.1 结构体大小计算过程

分步计算:

4.2 输出结果:

4.3 内存布局示意图:

4.4 总结:

4.5 优化

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

05-优化核心思路:


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

前言

定义一个结构体,结构体中有 uint8 uint16 uint32的成员变量,怎么计算这个结构体在内存中的大小?请举个例子详细解释

⭐在 C 语言中,结构体的内存大小计算需要考虑成员变量的大小内存对齐(Padding) 两个因素,而不仅仅是简单地将所有成员大小相加。内存对齐是编译器为了提高访问效率而采取的策略,不同成员类型有不同的对齐要求。

01-基本概念

  • 每个基本类型(如uint8_tuint16_tuint32_t)有其自然对齐值(通常等于其自身大小):
    • uint8_t(1 字节):对齐值为 1
    • uint16_t(2 字节):对齐值为 2
    • uint32_t(4 字节):对齐值为 4
  • 结构体的总大小必须是其最大成员对齐值的整数倍。⭐

02-示例说明

定义一个包含uint8_tuint16_tuint32_t的结构体,计算其内存大小:

#include <stdio.h>
#include <stdint.h> // 包含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. 成员auint8_t

    • 占 1 字节,从地址 0 开始存放(满足 1 字节对齐)。
    • 此时已用内存:1 字节。
  2. 成员buint16_t

    • 要求 2 字节对齐(地址必须是 2 的倍数)。
    • 上一个成员结束于地址 1,不满足对齐要求,因此需要填充 1 字节的空白(地址 1 处)。
    • b从地址 2 开始存放,占用地址 2-3(2 字节)。
    • 此时已用内存:1(a) + 1(填充) + 2(b) = 4 字节。
  3. 成员cuint32_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 auint16_t b之间填充了 1 字节的空白)。

我们可以通过打印成员的内存地址来更直观地看到对齐情况:

#include <stdio.h>
#include <stdint.h>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_tuint16_tuint8_tuint16_tuint32_t这 5 个成员的结构体大小,关键依然是内存对齐规则

3.1 示例结构体定义

#include <stdio.h>
#include <stdint.h>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. 成员auint8_t

    • 占 1 字节,地址 0(满足 1 字节对齐)
    • 已用内存:1 字节
  2. 成员buint16_t

    • 要求 2 字节对齐(地址需为 2 的倍数)
    • 上一个成员结束于地址 0,需填充 1 字节(地址 1)
    • b从地址 2 开始,占用地址 2-3(2 字节)
    • 已用内存:1 + 1(填充) + 2 = 4 字节
  3. 成员cuint8_t

    • 占 1 字节,地址 4(满足 1 字节对齐)
    • 已用内存:4 + 1 = 5 字节
  4. 成员duint16_t

    • 要求 2 字节对齐(地址需为 2 的倍数)
    • 上一个成员结束于地址 4,下一个地址 5 不是 2 的倍数,需填充 1 字节(地址 5)
    • d从地址 6 开始,占用地址 6-7(2 字节)
    • 已用内存:5 + 1(填充) + 2 = 8 字节
  5. 成员euint32_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_tuint8_tuint32_t等成员的结构体大小,重点依然遵循内存对齐规则 **。

4.1 结构体大小计算过程

结构体大小计算过程(地址从 0 开始)

#include <stdio.h>
#include <stdint.h>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. 成员buint16_t

    • 占 2 字节,从地址 0 开始(满足 2 字节对齐)
    • 已用内存:2 字节
  2. 成员duint16_t

    • 占 2 字节,从地址 2 开始(满足 2 字节对齐)
    • 已用内存:2 + 2 = 4 字节
  3. 成员auint8_t

    • 占 1 字节,从地址 4 开始(满足 1 字节对齐)
    • 已用内存:4 + 1 = 5 字节
  4. 成员cuint8_t

    • 占 1 字节,从地址 5 开始(满足 1 字节对齐)
    • 已用内存:5 + 1 = 6 字节
  5. 成员euint32_t

    • 要求 4 字节对齐(地址需为 4 的倍数)
    • 上一个成员结束于地址 5,下一个可用地址 6 不是 4 的倍数,需填充 2 字节(地址 6-7)
    • e从地址 8 开始,占用地址 8-11(4 字节)
    • 已用内存:6 + 2(填充) + 4 = 12 字节
  6. 成员fuint8_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-1b2 字节,uint16_t
2-3d2 字节,uint16_t
4a1 字节,uint8_t
5c1 字节,uint8_t
6-7填充(无数据)e对齐到 4 字节
8-11e4 字节,uint32_t
12f1 字节,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 <stdio.h>
#include <stdint.h>// 优化前的结构体(大小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. 成员euint32_t

    • 占 4 字节,从地址 0 开始(满足 4 字节对齐)
    • 已用内存:4 字节
  2. 成员buint16_t

    • 占 2 字节,从地址 4 开始(满足 2 字节对齐)
    • 已用内存:4 + 2 = 6 字节
  3. 成员duint16_t

    • 占 2 字节,从地址 6 开始(满足 2 字节对齐)
    • 已用内存:6 + 2 = 8 字节
  4. 成员auint8_t

    • 占 1 字节,从地址 8 开始(满足 1 字节对齐)
    • 已用内存:8 + 1 = 9 字节
  5. 成员cuint8_t

    • 占 1 字节,从地址 9 开始(满足 1 字节对齐)
    • 已用内存:9 + 1 = 10 字节
  6. 成员fuint8_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类型的bd放在一起,uint8_t类型的acf放在一起,减少跨类型对齐产生的填充。
  3. 最小化填充字节:优化后仅在结构体末尾需要 1 字节填充(而优化前需要 5 字节填充)。

通过合理调整成员顺序,在不改变功能的前提下,有效减少了结构体的内存占用。

http://www.dtcms.com/a/361498.html

相关文章:

  • 基于springboot生鲜交易系统源码和论文
  • 一文读懂k8s的pv与pvc原理
  • 威科夫与高频因子
  • 2.充分条件与必要条件
  • Android Framework打电话禁止播放运营商视频彩铃
  • Coze源码分析-工作空间-资源库-前端源码
  • Frida Hook 算法
  • 音频数据集采样率选择建议
  • 从网络层接入控制过渡到应用层身份认证的过程
  • 电源相关零碎知识总结
  • 如何把指定阿里云文件夹下的所有文件移动到另一个文件夹下,移动文件时把文件名称(不包括文件后缀)进行md5编码
  • @Autowired注入底层原理
  • 吴恩达机器学习补充:决策树和随机森林
  • AUTOSAR AP R24-11 Log and Trace 文档总结
  • 贪心算法解决钱币找零问题(二)
  • CentOS10安装RabbitMQ
  • [特殊字符]【C语言】超全C语言字符串处理函数指南:从原理到实战
  • ARM的编程模型
  • TikTok Shop 物流拖后腿?海外仓系统破解物流困局
  • nginx是什么?
  • MQ使用场景分析
  • OpenHarmony 分布式感知中枢深度拆解:MSDP 框架从 0 到 1 的实战指南
  • 2025年- H104-Lc212--455.分发饼干(贪心)--Java版
  • 电动自行车淋水安全测试的关键利器:整车淋水性能测试装置的技术分析
  • 零基础深度学习技术学习指南:从入门到实践的完整路径
  • 大语言模型对齐
  • 中宇联SASE解决方案荣获最佳实践奖,助力国际零售企业数字化转型
  • 像信号处理一样理解中断:STM32与RK3399中断机制对比及 Linux 驱动开发实战
  • Kali自带的录屏工具:recordmydesktop
  • 响应式编程框架Reactor【8】