【C语言】结构体内存对齐:热门面试话题

请添加图片描述
🔥引言

书接上文,我们了解关于结构体的基本知识,这篇将深入剖析结构体中一个重要的知识点:内存对齐
关于内存对齐是属于热门面试话题,对此单独放在一篇来分享

请添加图片描述

Alt

🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
请添加图片描述


一、结构体中内存对齐

1.1 对齐规则

  • 结构体第一个成员变量对齐相对于结构体成员地址偏移量为0的位置上

  • 其他成员变量需要对齐到对齐数的整数倍

  • 结构体总大小最大对齐数的正数倍

如果存在嵌套结构体的情况,嵌套结构体占用空间需要对齐自身最大对齐数的整数倍,同时在计算结构体总大小的时候,嵌套结构体的最大对齐数参与比较

注意】:对齐数 == 编译器默认的一个对齐数与该成员变量大小的较小值

  • 在vs环境下,系统默认对齐为8

  • 在Linux中没有默认对齐数,对齐数就是成员自身的大小


通过题目熟练的掌握以上知识.

struct S1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct S1));--12

struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));--8

struct S4
{
    char c1;
    struct S2 s2;
    double d;
};
printf("%d\n", sizeof(struct S2));--24

在这里插入图片描述
在这里插入图片描述

说明】:数值代表的是结构体变量地址处的偏移量


1.2 内存对齐的意义

⼤部分的参考资料都是这样说的

平台原因(移植原因)

  • 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

性能原因

  • 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;对齐的内存访问仅需要⼀次访问。

假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对齐是拿空间来换取时间的做法

!(https://img-blog.csdnimg.cn/direct/db9b7a7f327947f7a81d64a1a2254b68.gif)

通过上述的观察,不难看出。如果不存在内存对齐,需要执行两个内存访问(对象被分放在两块内存块),而内存对齐只需要进行一次。

对此在涉及结构体时,需要考虑满足对齐,又要节省空间。可以将占用空间小的成员尽量集中在一起

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

1.3 #pragma(预处理指令)

1.3.1 pragma相关介绍

  • 用于指定计算机或操作系统特定的编译器功能
  • 根据定义pragma指令是计算机或操作系统特定的,并且通常对于每个编译器而言都有所不同
  • pragma指令可用于条件语句以提供新的预处理器功能,或为编译器提供实现所定义的信息,

1.3.2 #pragma pack(n)修改默认对齐数

#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;
}

推荐使用场景,在结构体进行内存对齐时,如果对于对齐方式不能达到预期,可以通过该指令更改默认对齐数

获得该成员变量的偏移量

这里需要使用一个函数offsetof()宏,该函数被声明在stddef.h文件中,以下是函数offsetof()宏

size_t offsetof(type,member);

宏定义】:

#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE*)0)->MEMBER)

如果想要了解更多,可以参考下这篇博客Offsetof宏详解-CSDN博客.这里只如何去使用Offsetof()宏计算出结构体某成员地址的偏移量。

#include <stdio.h>
#include <stddef.h>
struct S
{
    char a;
    int i;
};
int main()
{
    printf("%d\n",offsetof(struct S,i));
    //那么这里的结果就是就是4
    return 0;
}

小总结】:
结构体中的内存对齐是为了以空间换取时间的做法,随着计算机不断地更新换代,一般不需要担心内存空间不足的问题,逐渐地从更多考虑的是时间上的问题。同时为了节约空间的开销,提出位段


二、结构体实现位段

2.1 位段的概念

位段是结构体的一种变形,在功能、用法上与结构体基本一致,但是在于内存分配上不同,位段可以很好的节省空间,可存在位段跨平台的问题。同时与结构体相比有两个点不同。

  • 成员上:intunsigned intsigned int,但是在C99中是可以选择其他类型
  • 格式上:位段成员名后面有一个冒号和一个数字
struct A
{
    char _a:2;
    char _b:5;
};

【说明】:这里数字代表的是该成员变量占用空间大小,而大小单位是比特

【问题】:位段A所占的内存大小是多大?

这个问题,需要利用下面的知识了


2.2 位段的内存分配

  • 位段成员:intunsigned intsigned int或者char等类型(需要是整形,是要转换为二进制
  • 位段开辟空间的大小一般是以四个字节或一个字节开辟的
  • 位段涉及许多不确定的因素,位段是不跨平台的,注意可移植的程序,应该避免使用位段
struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};

struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

2.3 位段的跨平台问题

不确定的因素大致包括】:

  1. 内存存放的方向是从左到右,还是从右到左
  2. 是低地址到高地址,还是高地址到低地址
  3. int类型是不确定是被当作有符号数还是无符号数
  4. 当一个结构体包括了两个位段,第二个位段比较大,无法容纳第一个位段剩下的空间,是舍弃还是利用剩下的空间,这是不确定的
  5. 位段中最大位的数目不能确定(16位机器最大16,32位机器最⼤32,写成27,在16位机器会出问题),可能会冲出最大的范围,出现问题

我们不妨以vs2013环境下测量下数据

vs2013下,位段是从左到右,从低地址到高地址,位段需要的空间不足,直接开辟一块新的空间,我们来结合图片理解下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

【步骤】:

  1. 位段开辟八个bit位(这里是char类型的情况)
  2. 位段成员后面数字是占用多少bit位
  3. 根据变量数据,转化为二级制(一个二级制为一个比特位),根据位段对应的数据,将转为的二级制多个比特位放入
  4. 关于上不确定因素中(4),vs2013选择舍弃,那就开辟一块新的空间,重复(1,2,3)步骤

2.4 位段的应用

比如下图中网络协议中,在一个结构存在很多只需要几个bit位就能实现的效果,这里使用位段就能达到想要的效果,也能节省空间的浪费。同时网络传输的数据大小也会小一点,提高了网络的流畅和效率!

位段使用注意事项】:

struct A
{
    int _a:2;
    int _b:5;
};
int main()
{
    //错误的做法
    struct A s={0};
    scanf("%d",&s._a);

    //正确的示范
    int b=0;
    scanf("%d",&b);
    s._b=b;
    return 0;
}

说明】:

位段的几个成员共有同一个字节,而有些成员的起始位置并不是某个字节的起始位置。对此这些位置是没有地址(内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的

解决办法】:

可以将值放入一个变量中,再通过赋值给位段成员,这个赋值在以后的操作中,是很巧妙的用法的。


在这里插入图片描述
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二C语言笔记,希望对你在学习C语言中有所帮助!

  • 158
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 135
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是店小二呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值