[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_t
、uint16_t
、uint32_t
)有其自然对齐值(通常等于其自身大小):uint8_t
(1 字节):对齐值为 1uint16_t
(2 字节):对齐值为 2uint32_t
(4 字节):对齐值为 4
- 结构体的总大小必须是其最大成员对齐值的整数倍。⭐
02-示例说明
定义一个包含uint8_t
、uint16_t
、uint32_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 计算过程分析:
-
成员
a
(uint8_t
):- 占 1 字节,从地址 0 开始存放(满足 1 字节对齐)。
- 此时已用内存:1 字节。
-
成员
b
(uint16_t
):- 要求 2 字节对齐(地址必须是 2 的倍数)。
- 上一个成员结束于地址 1,不满足对齐要求,因此需要填充 1 字节的空白(地址 1 处)。
b
从地址 2 开始存放,占用地址 2-3(2 字节)。- 此时已用内存:1(a) + 1(填充) + 2(b) = 4 字节。
-
成员
c
(uint32_t
):- 要求 4 字节对齐(地址必须是 4 的倍数)。
- 上一个成员结束于地址 3,下一个地址 4 正好满足 4 字节对齐。
c
从地址 4 开始存放,占用地址 4-7(4 字节)。- 此时已用内存:4 + 4(c) = 8 字节。
-
结构体总大小:
- 最大成员对齐值为 4(
uint32_t
),8 是 4 的整数倍,无需额外填充。 - 最终大小:8 字节。
- 最大成员对齐值为 4(
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 <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-0x33c
从地址 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 <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 开始)
-
成员
a
(uint8_t
):- 占 1 字节,地址 0(满足 1 字节对齐)
- 已用内存:1 字节
-
成员
b
(uint16_t
):- 要求 2 字节对齐(地址需为 2 的倍数)
- 上一个成员结束于地址 0,需填充 1 字节(地址 1)
b
从地址 2 开始,占用地址 2-3(2 字节)- 已用内存:1 + 1(填充) + 2 = 4 字节
-
成员
c
(uint8_t
):- 占 1 字节,地址 4(满足 1 字节对齐)
- 已用内存:4 + 1 = 5 字节
-
成员
d
(uint16_t
):- 要求 2 字节对齐(地址需为 2 的倍数)
- 上一个成员结束于地址 4,下一个地址 5 不是 2 的倍数,需填充 1 字节(地址 5)
d
从地址 6 开始,占用地址 6-7(2 字节)- 已用内存:5 + 1(填充) + 2 = 8 字节
-
成员
e
(uint32_t
):- 要求 4 字节对齐(地址需为 4 的倍数)
- 上一个成员结束于地址 7,下一个地址 8 正好是 4 的倍数
e
从地址 8 开始,占用地址 8-11(4 字节)- 已用内存:8 + 4 = 12 字节
-
结构体总大小:
- 最大成员对齐值为 4(
uint32_t
) - 12 是 4 的整数倍,无需额外填充
- 最终大小:12 字节
- 最大成员对齐值为 4(
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 <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;
}
分步计算:
-
成员
b
(uint16_t
):- 占 2 字节,从地址 0 开始(满足 2 字节对齐)
- 已用内存:2 字节
-
成员
d
(uint16_t
):- 占 2 字节,从地址 2 开始(满足 2 字节对齐)
- 已用内存:2 + 2 = 4 字节
-
成员
a
(uint8_t
):- 占 1 字节,从地址 4 开始(满足 1 字节对齐)
- 已用内存:4 + 1 = 5 字节
-
成员
c
(uint8_t
):- 占 1 字节,从地址 5 开始(满足 1 字节对齐)
- 已用内存:5 + 1 = 6 字节
-
成员
e
(uint32_t
):- 要求 4 字节对齐(地址需为 4 的倍数)
- 上一个成员结束于地址 5,下一个可用地址 6 不是 4 的倍数,需填充 2 字节(地址 6-7)
e
从地址 8 开始,占用地址 8-11(4 字节)- 已用内存:6 + 2(填充) + 4 = 12 字节
-
成员
f
(uint8_t
):- 占 1 字节,从地址 12 开始(满足 1 字节对齐)
- 已用内存:12 + 1 = 13 字节
-
结构体总大小:
- 最大成员对齐值为 4(
uint32_t
) - 当前已用 13 字节,需要填充 3 字节使总大小为 4 的整数倍(13 + 3 = 16)
- 最终大小:16 字节
- 最大成员对齐值为 4(
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 <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 开始):
-
成员
e
(uint32_t
):- 占 4 字节,从地址 0 开始(满足 4 字节对齐)
- 已用内存:4 字节
-
成员
b
(uint16_t
):- 占 2 字节,从地址 4 开始(满足 2 字节对齐)
- 已用内存:4 + 2 = 6 字节
-
成员
d
(uint16_t
):- 占 2 字节,从地址 6 开始(满足 2 字节对齐)
- 已用内存:6 + 2 = 8 字节
-
成员
a
(uint8_t
):- 占 1 字节,从地址 8 开始(满足 1 字节对齐)
- 已用内存:8 + 1 = 9 字节
-
成员
c
(uint8_t
):- 占 1 字节,从地址 9 开始(满足 1 字节对齐)
- 已用内存:9 + 1 = 10 字节
-
成员
f
(uint8_t
):- 占 1 字节,从地址 10 开始(满足 1 字节对齐)
- 已用内存:10 + 1 = 11 字节
-
结构体总大小:
- 最大成员对齐值为 4(
uint32_t
) - 当前已用 11 字节,需填充 1 字节使总大小为 4 的整数倍(11 + 1 = 12)
- 最终大小:12 字节(比优化前减少 4 字节)
- 最大成员对齐值为 4(
05-优化核心思路:
- 优先放置高对齐要求的成员:将
uint32_t e
(4 字节对齐)放在最前面,避免后续成员为了对齐它而产生大量填充。 - 同类对齐成员集中放置:将
uint16_t
类型的b
和d
放在一起,uint8_t
类型的a
、c
、f
放在一起,减少跨类型对齐产生的填充。 - 最小化填充字节:优化后仅在结构体末尾需要 1 字节填充(而优化前需要 5 字节填充)。
通过合理调整成员顺序,在不改变功能的前提下,有效减少了结构体的内存占用。