C | 结构体内存对齐

简介: 结构体内存对齐历来是C语言学习过程中的重点,其目的是通过牺牲空间来换取时间,但是理解起来一点也不难,那我们就,学学?

在这里插入图片描述
啊我摔倒了..有没有人扶我起来学习....


@TOC


前言

结构体内存对齐历来是C语言学习过程中的重点,其目的是通过牺牲空间来换取时间,但是理解起来一点也不难,那我们就,学学?


一、结构体内存对齐是什么?

这是一种结构体存储在内存里的一套规则,具体如下:

  1. 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处
  2. 从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处

==对齐数:结构体成员自身大小和默认对齐数的较小值==
VS:8
Linus环境默认不设对齐数(对齐数是结构体成员的自身大小)

  1. 结构体的总大小,必须是最大对齐数旳整数倍

每个结构体成员都有一个对齐数,==其中最大==的对齐数就是最大对齐数

  1. 如果嵌套了结构体的情况:

嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

好了,是不是啥都没看懂?别慌,下面开始正餐


二、认识结构体内存对齐

1. 先来一个思考

#include<stdio.h>

struct S1
{
    char c1;
    int i;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int i;
};

int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));

    return 0;
}
  • 路飞:以上代码的输出结果是什么?
  • 贝吉塔:

在这里插入图片描述

  • 路飞:那让我们运行代码看看!

在这里插入图片描述

  • 贝吉塔:??????????
  • 贝吉塔:这是怎么回事?!

2. 用offsetof协助一手

  • 为了让我们深入了解,我们先学一个宏:
#include <stddef.h>
offsetof

在这里插入图片描述

  • 它是用来计算结构体成员相对于起始位置的偏移量。起始位置就是结构体在内存中刚开始存储的那个字节,我们假设为0,偏移量就是相对于0偏移几个字节,比如偏移量是3。

在这里插入图片描述

  • 我们用它计算一下
#include <stdio.h>
#include <stddef.h>

struct S1
{
    char c1;
    int i;
    char c2;
};

int main()
{
    printf("%d\n", offsetof(struct S1,c1));
    printf("%d\n", offsetof(struct S1,i));
    printf("%d\n", offsetof(struct S1,c2));

    return 0;
}
  • 输出结果为
成员 Value
c1 0
i 4
c2 8
  • 那我们就来看看究竟是怎么一回事~

3. 研究对齐规则

  • 根据上述结果,struct s1的成员一共占12个字节,结合偏移量,那么它们在内存中应该是这样存放的

在这里插入图片描述

  • 说明其中有些空间是浪费掉的
  • 我们继续观察上图:
  1. 参考内存对齐规则1(结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处)得出,c1确实在0偏移量处
  2. 参考内存对齐规则2(从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处),由于博主使用的是VS,默认对齐数为8,而每个成员自身大小与默认对齐数进行比较,较小值就是该成员的对齐数了,因此列出下表
成员 自身大小 默认对齐数 对齐数(较小值)
c1 1 8 1
i 4 8 4
c2 1 8 1

因此,i的对齐数为4,所以它存储的时候不能紧接在c1的背后,只能从偏 移量为4(对齐数的整数倍)处开始存储,往后占据4个字节

  1. 然后c2就紧接在i后边存储,此时偏移量为8,因为c2的对齐数是1嘛,偏移量8不就是1的整数倍嘛
  2. 再参考内存对齐规则3(结构体的总大小,必须是最大对齐数旳整数倍)可知,要先在结构体成员中找出对齐数的老大,struct S1里老大是4,那么4就是最大对齐数,此时结构体总大小就得是4的整数倍,而此时c2存储完之后是在偏移量为8处,struct S1一共占用了9个字节,所以必须往后开辟空间,即使浪费也在所不惜

那么用struct S2练练手吧,计算出结构体的大小~

struct S2
{
    char c1;
    char c2;
    int i;
};

在这里插入图片描述

答案是8

趁热打铁,我们继续!

struct S3
{
    double d;
    char c;
    int i;
};

在这里插入图片描述

答案是16

扶我起来!我还要继续

struct S4
{
    char c4;
    struct S3 s3;
    double d4;
};
  • 这个就有点难度了,不怕,我们还有对齐规则4(嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍),看起来挺绕,其实说白了都是找最大对齐数,细心一点就很容易了

    此时偏移量8至23存储的其实就是s3,只是s3里面又存储了d、c、i

在这里插入图片描述

答案是32

三、为什么要有结构体内存对齐?

1. 存在的必要

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

针对性能原因我们试着解释一下

假设每次访问内存4个字节,不对齐的话访问i时需要两次

在这里插入图片描述

  • ==所以说结构体内存对齐是用空间换时间的做法==

2. 修改默认对齐数

  • 假如你就是不想浪费空间,咱可以用下列代码取消默认对齐数
#pragma pack(1)
  • 其实原理就是设置括号里的数字为新的默认对齐数,设为1的话相当于没有对齐的概念,所以同样可以设成其他数字(一般是2的整数倍),但是使用后想恢复的话,再用一次
#pragma pack()
  • 此时括号里不填

四、小小的总结

其实多尝试比较的话是可以看出来,拥有同样成员的结构体,所占用的内存是不一样的

struct S1
{
    char c1;
    int i;
    char c2;
};

struct S2
{
    char c1;
    char c2;
    int i;
};

刚刚一起分析过的,struct S1大小是12,struct S2是8,通过观察,以后写结构体,==尽量把占用内存较小的变量放在前面==


在这里插入图片描述

相关文章
|
6月前
|
编译器 Linux C语言
结构体内存对齐
结构体内存对齐
53 0
|
6月前
|
编译器 Linux C语言
详解结构体内存对齐及结构体如何实现位段~
详解结构体内存对齐及结构体如何实现位段~
|
6月前
|
存储 编译器 C语言
自定义类型:结构体(自引用、内存对齐、位段(位域))
自定义类型:结构体(自引用、内存对齐、位段(位域))
|
6月前
|
存储 编译器 C语言
结构体的内存对齐与位段
当我们描述一个人的年龄时我们可以使用,int age = 18;但是如果我们要描述一个人呢?很显然我们无法仅靠一个age就实现对一个人的描述,所以就有了结构体,在结构体中我们可以包含多种类型的数据,这样就可以实现对一个人的描述比如身高、爱好、体重等等
|
编译器 C++
计算结构体大小:内存对齐详解
计算结构体大小:内存对齐详解
202 0
|
编译器 Linux C++
结构体的内存对齐
结构体的内存对齐
|
编译器 C++
结构体内存对齐问题
结构体重点😃 1.结构体内存对齐问题,是在计算结构体的大小时,对结构体成员在内存中的位置进行研究的问题。
|
C++
【关于结构体内存对齐问题】(下)
【关于结构体内存对齐问题】
102 0
|
编译器 C++
【关于结构体内存对齐问题】(上)
【关于结构体内存对齐问题】
116 0
|
编译器 Linux C语言
结构体的内存对齐与位段的实现
注意上面这两种结构体都是属于匿名结构体类型,不告诉你名字,这种结构体类型如果要使用必须在声明的时候就在后面定义变量,不能再到主函数里面引用,因为你不知道这个结构体的名字是什么,所以必须在声明的时候就定义变量。
89 0