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

发布于:2025-09-09 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

前言

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-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 开始):

  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 字节填充)。

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


网站公告

今日签到

点亮在社区的每一天
去签到