当前位置: 首页 > news >正文

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

请添加图片描述
🔥引言

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

请添加图片描述

Alt

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

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

文章目录

  • 一、结构体中内存对齐
    • 1.1 对齐规则
    • 1.2 内存对齐的意义
    • 1.3 #pragma(预处理指令)
      • 1.3.1 pragma相关介绍
      • 1.3.2 #pragma pack(n)修改默认对齐数
  • 二、结构体实现位段
    • 2.1 位段的概念
    • 2.2 位段的内存分配
    • 2.3 位段的跨平台问题
    • 2.4 位段的应用


一、结构体中内存对齐

1.1 对齐规则

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

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

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

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

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

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

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


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

struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));--12struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));--8struct 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));//那么这里的结果就是就是4return 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语言中有所帮助!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • day15
  • 百度 提前批 国际化广告部 (深圳-机器学习/数据挖掘/自然语言处理工程师) 一面+二面面经
  • 记录centos中操作(查找、结束、批量)进程以及crontab定时写法的知识
  • 数据库SQL语言实战(十)(最后一篇)
  • c++ set/multiset容器
  • 大数据智慧消防解决方案(24页PPT)
  • 【华为OD机试-C卷D卷-200分】二叉树的广度优先遍历(C++/Java/Python)
  • LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)
  • 亲测使用frp获得访问者真实ip
  • 【WEB前端2024】开源智体世界:乔布斯3D纪念馆-第28课-avatar玩家3D形象
  • Java学习四
  • 通付盾Web3专题 | SharkTeam:Web3常见钓鱼方式分析与安全防范建议
  • 神经网络中的归一化操作
  • 层次式架构设计理论与实践
  • kafka 案例
  • 【css3】浏览器内核及其兼容性
  • 5、React组件事件详解
  • JAVA多线程机制解析-volatilesynchronized
  • mongodb--安装和初步使用教程
  • PAT A1120
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Rancher如何对接Ceph-RBD块存储
  • react 代码优化(一) ——事件处理
  • Redis学习笔记 - pipline(流水线、管道)
  • Spring Boot快速入门(一):Hello Spring Boot
  • 阿里云Kubernetes容器服务上体验Knative
  • 半理解系列--Promise的进化史
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 如何在GitHub上创建个人博客
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 算法-插入排序
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • #100天计划# 2013年9月29日
  • #define、const、typedef的差别
  • #HarmonyOS:Web组件的使用
  • #VERDI# 关于如何查看FSM状态机的方法
  • (1)常见O(n^2)排序算法解析
  • (C++17) std算法之执行策略 execution
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (六)软件测试分工
  • (三)mysql_MYSQL(三)
  • (三分钟)速览传统边缘检测算子
  • (转)MVC3 类型“System.Web.Mvc.ModelClientValidationRule”同时存在
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .NET Framework杂记