超详细——结构体大小计算(内存对齐)

一:结构体内存对齐

1.引例

  • 开幕雷击,请给出下列四个结构体的大小
  • 请添加图片描述
  • 我们可能第一时间都会有个朴实的想法,把每个变量的大小加一起就是结构体的大小,那上面这四个题的答案应该是6,6,13,22。
  • 我在没有学习结构体内存对齐的时候,也是这么计算的,不过编译器会告诉我们,我们的答案是错的。请添加图片描述
  • 答案是12,8,16,32,这是为什么?为了能够正确计算结构体的大小,我们需要知道结构体的内存对齐。

2.结构体内存对齐的规则

  1. 结构体的第一个成员,放在结构体变量在内存中存储位置的偏移量为0的地址处。
  2. 从第2个成员往后的所有成员,都放在一个对齐数的整数倍地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的值为8,Linux中没有默认对齐数(但按照默认对齐数为4计算通常能得到正确的结果)
  3. 结构体总大小为所有成员中最大对齐数的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  • 掌握了以上规则,我们就能正确计算出一个结构体的大小。
  • 3.练习1解答

  • 现在我们运用上面的四个规则分别计算上文中4个结构体的大小,首先是第一题。
  • 请添加图片描述
    请添加图片描述
  • 首先我们根据规则1,结构体第一个成员放在结构体变量在内存中存储位置的0偏移处的地址处,所以第一个变量c1放在了0偏移处。
  • 而c1的类型为char,我们知道char类型的大小为1字节,所以 c1变量占据了一个字节的位置
  • 图中一个矩形代表一个字节大小,右边的数字代表相对于结构体在内存中存储位置的偏移大小
  • 好了,现在第一个变量 c1放置完了,我们开始放第二个变量i,根据规则2
  • 从第二个成员往后的所有成员,都放在一个对齐数的整数倍的地址处
  • 而对齐数是默认对齐数和成员大小中的较小值
  • int 类型的大小为4字节,默认对齐数为8,所以变量i的对齐数是4,我们需要将变量i放在它对齐数的整数倍处,1,2,3都不是4的整数倍,所以这些位置什么也不放,直到偏移量为4时,我们开始放变量i
  • 而int大小为4个字节,所以4,5,6,7这四个位置放我们的变量i
  • 最后放变量c2,c2的类型为char,大小为1字节,1<8,故c2的对齐数为1,任何数都是1的倍数,所以我们直接将c2放在偏移量为8的地址处
  • 这样我们所有的变量都放置完毕了,从地址0到8,一共9个字节,那这个结构体的大小就是9个字节吗?
  • 不是,根据规则3,结构体总大小为所有成员中最大对齐数的整数倍。
  • 三个成员的对齐数分别为1,4,1。最大为4。
  • 所以结构体的大小应该是4的整数倍,离9最近的4的倍数为12,故这个结构体的大小为12。

4.练习2解答

请添加图片描述
请添加图片描述

  • 这个结构体S2的成员和结构体S1一模一样,但是创建时c2在i前面,那这个结构体S2的大小和S1一样大吗?
  • 首先根据规则1,我们将变量c1放在偏移量为0处,占一个字节
  • 然后放c2,根据规则2,我们要把c2放在他对齐数的整数倍的的地址处,c2的大小为1,默认对齐数为8,故c2的对齐数为1,1是1的倍数,可以放c2,c2大小为1个字节,只占据偏移量为1的地址处。
  • 最后放变量i,大小为4字节,默认对齐数为8,4<8,对齐数为4,从偏移量为4的地址处开始存放,放4个字节,占据4,5,6,7
  • 从0到7,总共8个字节,所有成员变量的对齐数分别为1,1,4,最大为4,8是4的倍数。根据规则3,结构体S2的大小就为8个字节
  • 可以看到,结构体S1和S2成员变量均相同,但是创建的顺序不同,也会导致结构体的大小的不同
  • 那在设计结构体的时候,我们既要满足对齐,又要节省空间,我们应该让占用空间小的成员尽量集中在一起

5.练习3解答

  • 请添加图片描述
    请添加图片描述
  • 首先根据规则1,我们将变量d放在偏移量为0处,变量d类型为double,占8个字节
  • 占据0~7这8个地址
  • 然后放c,根据规则2,我们要把c放在他对齐数的整数倍的的地址处,c的大小为1,默认对齐数为8,故c的对齐数为1,8是1的倍数,可以放c,c大小为1个字节,只占据偏移量为8的地址处。
  • 最后放变量i,大小为4字节,默认对齐数为8,4<8,对齐数为4,9,10,11都不是4的倍数,从偏移量为12的地址处开始存放,放4个字节,占据12,13,14,15
  • 从0到15,总共16个字节,所有成员变量的对齐数分别为8,1,4,最大为8,16是8的倍数。根据规则3,结构体S2的大小就为16个字节

6.练习4讲解

请添加图片描述
请添加图片描述

  • 可以看到结构体S4中包含了结构体S3,这与我们之前计算的结构体有所不同。
  • 我们还是按照老套路来计算,首先根据规则1,我们将变量c1放在偏移量为0处,变量c1类型为char,占1个字节,占据偏移量为0的地址
  • 然后放s3,根据规则2,我们要把s3放在他对齐数的整数倍的的地址处,s3的类型为struct S3,根据规则4
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,之前我们计算过结构体S3的最大对齐数为8,故变量s3的对齐数为8,1-7 均不是8的倍数,从偏移量为8的地址处开始存放s3,结构体S3的大小为16字节,占据8~23偏移量的地址
  • 最后放变量d,大小为8字节,默认对齐数为8,对齐数为8, 24是8的倍数,从此存放变量d,存放8个字节,占据24~31偏移量的地址。
  • 从0到31,总共32个字节,根据规则4,如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  • 所有成员变量的对齐数分别为1,8,8,最大为8, 32是8的倍数。结构体S2的大小就为32个字节。
  • 相信通过上文的4个练习讲解,你已经学会如何计算结构体的大小了。

二:为什么存在结构体的内存对齐

  1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
    总体来说:
    结构体的内存对齐是拿空间来换取时间的做法。

三:修改默认对齐数

  • 我们有时候可能觉得根据编译器设计的默认对齐数不合我们心意,我们可以通过预处理指令#pragma来修改我们的默认对齐数

请添加图片描述

  • 可以看到我们将默认对齐数改成1,这样其实就不用对齐了,我们得出的结果就和我们刚开始想的一样,结构体大小就是各变量大小相加
  • 我们可以在一个结构体创建前使用#pragma pack(想要修改的数值)来改变默认对齐数,然后再这个结构体创建完之后#pragma pack()括号中为空,来取消我们之前设置的对齐数,还原为默认对齐数
    请添加图片描述
  • 如图,只有S1,S2的默认对齐数被修改为1,S3和S4的不变
  • 最后附上代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//练习1
struct S1
{
    char c1;
    int i;
    char c2;
};
//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
//练习3
struct S3
{
    double d;
    char c;
    int i;
};
//练习4-结构体嵌套问题
struct S4
{
    char c1;
    struct S3 s3;
    double d;
};
int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    printf("%d\n", sizeof(struct S3));
    printf("%d\n", sizeof(struct S4));
    return 0;
}
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dhdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值