结构体内存对齐

在我们已经掌握了结构体的基本使⽤之后,现在我们需要深⼊讨论⼀个问题:计算结构体的⼤⼩。
这也是⼀个特别热⻔的考点: 结构体内存对⻬

1:对齐规则

⾸先得掌握结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
- VS 中默认的值为 8
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
举个例子:
struct Example {
    char a;    // 1字节,存放在0地址
    int b;     // 4字节,需对齐到4的倍数地址(存放于4-7地址)
    short c;   // 2字节,对齐到2的倍数地址(存放于8-9地址)
};

这个结构体实际占用 12 字节(而非 1+4+2=7 字节),因为 char 和 int 之间有 3 字节的填充(padding),结构体末尾可能也有填充以满足整体对齐要求。(后面会讲具体计算方法)

 

不同编译器和平台可能有不同的默认对齐规则,可通过编译指令(如#pragma pack)调整对齐方式,但不当的调整可能导致性能下降或兼容性问题。

2:对齐值和结构体实际所占内存的区别与联系

对齐值和结构体所占内存是两个紧密相关但又不同的概念,它们共同描述了结构体在内存中的布局特性。

核心区别:

概念定义单位特点
对齐值结构体成员或整个结构体在内存中存放时的地址规则(必须是某个值的整数倍)。字节是一种地址约束规则,描述 “如何存放”,不直接表示占用空间大小。
结构体所占内存结构体在内存中实际占据的总字节数(包括成员大小和填充字节)。字节实际占用的空间量,描述 “占用多少”,由成员大小和对齐规则共同决定。

联系:

对齐值是计算结构体所占内存的核心依据,两者通过 “填充字节(padding)” 关联:

 

结构体成员的对齐值决定了成员之间是否需要填充字节(为了满足地址约束)。

整个结构体的总大小必须是其最大成员对齐值的整数倍(整体对齐规则),因此结构体末尾可能需要额外填充。

这里举个例子来分别计算结构体的对齐值和内存:

struct Demo {
    char x;   // 对齐值1字节,存于地址0
    int y;    // 对齐值4字节,需从地址4开始(0-3之间填充3字节)
    short z;  // 对齐值2字节,从地址8开始(占用8-9)
};

成员对齐值:x=1y=4z=2,结构体整体对齐值 = 最大成员对齐值 = 4。

结构体所占内存:从0开始,char x占了一个字节,存于地址0,int y占四个字节,由于每个成员的起始地址必须是其自身对齐值(通常等于自身大小)的整数倍。,所以它从地址4开始存储,占了四个字节,到地址7。由于地址8本身就是 short z的对齐值2的倍数,所以可以直接从地址8存储。最后结构体占用到了地址9,由于整个结构体的总大小必须是其最大成员对齐值的整数倍,所以结构体占了12个字节。

3:为什么要存在内存对齐

⼤部分的参考资料都是这样说的:
平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。
性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:

让占⽤空间⼩的成员尽量集中在⼀起

1 //例如:
2 struct S1
3 {
4 char c1;
5 int i;
6 char c2;
7 };
8
9 struct S2
10 {
11 char c1;
12 char c2;
13 int i;
14 };

虽然S1 S2 类型的成员⼀模⼀样,但是 S1 S2 所占空间的⼤⼩有了⼀些区别。

4:结构体嵌套

#include <stdio.h>

// 内部结构体
struct Inner {
    char a;    // 1字节,对齐值1
    int b;     // 4字节,对齐值4
};  // 内部结构体大小:1 + 3(填充) + 4 = 8字节(最大成员对齐值4,8是4的倍数)

// 外部结构体(嵌套了Inner)
struct Outer {
    short x;      // 2字节,对齐值2
    struct Inner y; // 嵌套的结构体,对齐值=其内部最大成员对齐值4
    char z;       // 1字节,对齐值1
};

int main() {
    printf("Inner size: %zu\n", sizeof(struct Inner));   // 输出8
    printf("Outer size: %zu\n", sizeof(struct Outer));   // 输出16
    return 0;
}

内存布局分析:

  1. 内部结构体struct Inner的布局

    • char a 占用地址 0(满足 1 字节对齐)
    • 填充 3 字节(地址 1-3)
    • int b 占用地址 4-7(满足 4 字节对齐)
    • 总大小 8 字节(是最大对齐值 4 的倍数)
  2. 外部结构体struct Outer的布局

    • short x 占用地址 0-1(满足 2 字节对齐)
    • 填充 2 字节(地址 2-3),因为接下来的struct Inner y需要 4 字节对齐
    • struct Inner y 占用地址 4-11(共 8 字节,满足 4 字节对齐)
    • char z 占用地址 12(满足 1 字节对齐)
    • 填充 3 字节(地址 13-15),使总大小为 16 字节(外部结构体最大对齐值是 4,16 是 4 的倍数)

关键规则:

  • 嵌套的结构体成员,其对齐值等于自身内部最大成员的对齐值(而非结构体本身的大小)
  • 外部结构体的总大小必须是所有成员(包括嵌套结构体)的最大对齐值的整数倍
  • 嵌套结构体内部的填充和外部的填充是相互独立的,都会影响最终总大小
 

这个例子中,外部结构体因为嵌套了需要 4 字节对齐的内部结构体,最终总大小被 “拉齐” 到 16 字节,而不是简单的 2+8+1=11 字节。

5:修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
 //输出的结果是什么?
 printf("%d\n", sizeof(struct S));
 return 0;
}

结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值