详解C语言结构体内存对齐:你知道如何快速计算结构体大小吗?

news2024/11/6 7:06:32

本篇博客会讲解C语言结构体的内存对齐,并且给出一种快速计算结构体大小的方式。主要讲解下面几点:

  1. 结构体的内存对齐是什么?
  2. 如何快速计算结构体的大小?
  3. 如何利用内存对齐节省结构体占用的内存空间?
  4. 为什么结构体要内存对齐?
  5. 如何修改默认对齐数?

结构体内存对齐

结构体内存对齐是什么?

结构体内有一个或者多个成员变量,这些成员变量是要“对齐”的。这么说可能有点抽象,我们先来了解一下内存对齐的规则,以及几个概念。

  1. 每个成员变量都有一个“对齐数”,这个对齐数等于其自身大小和默认对齐数的较小值。

举个例子:

struct S
{
	int a;
	char c;
	double d;
];

对于上面这个结构体,有3个成员变量,每个成员变量都有一个对齐数。默认对齐数是什么,我们下一条规则再说,先假设默认对齐数是4。

  • a的大小是4,默认对齐数是4,取两者的较小值,得到a的对齐数是4。
  • c的大小是1,默认对齐数是4,取两者的较小值,得到c的对齐数是1。
  • d的大小是8,默认对齐数是4,取两者的较小值,得到d的对齐数是4。
  1. 默认对齐数是由编译器决定的。如:VS的默认对齐数是8,gcc没有默认对齐数。如果没有默认对齐数,每个成员变量的对齐数就是其自身大小(或者也可以假设默认对齐数是正无穷,取自身大小和默认对齐数的较小值,也是自身大小)。关于如何修改默认对齐数,本篇博客后面会讲解。
  2. 每个成员变量都有一个“偏移量”。这个偏移量指的是,该成员变量的起始地址与结构体的起始地址相差了几个字节。成员变量会按照声明的顺序,地址从低到高变化。第一个成员变量的偏移量是0,也就是说,第一个成员变量的起始地址和结构体的起始地址相同。或者也可以这样理解,对于一个结构体的空间内,每一个地址都对应一个偏移量,这个偏移量就是该地址和结构体的起始地址相差的字节数。
  3. 从第二个成员变量开始,偏移量是其对齐数的整数倍。

建议反复阅读规则1~4,再结合下面的例子来理解。

还是上面的结构体,我再写一遍,大家就不用向上翻了。

struct S
{
	int a;
	char c;
	double d;
];

假设默认对齐数是8,根据前面的计算,a、c、d的对齐数分别是4、1、4。

  • a作为第一个成员变量,偏移量是0。由于其大小是4,会占用偏移量是0、1、2、3的地址处。
  • 接下来可用的位置是偏移量为4的地址处,由于4就是c的对齐数的整数倍,故c会占用偏移量是4的位置,并且只占用这一个字节,因为c的大小是1个字节。
  • 接下来可用的位置是偏移量为5的地址处,注意,由于5不是d的对齐数的整数倍,接下来的6、7也不是,所以d的偏移量是8,并且占用8个字节,因为d的大小是8个字节,占用了偏移量为8、9、10、11、12、13、14、15的地址处。

综上所述,a占用偏移量为0、1、2、3的地址处,c占用偏移量为4的地址处,偏移量为5、6、7的地址处被浪费了,接下来d占用偏移量为8、9、10、11、12、13、14、15的地址处。由于有的位置被浪费了,这种浪费一定的空间,使得每个成员变量的偏移量都对齐到其对齐数的整数倍处的现象,就是内存对齐。

此时,这个结构体的大小是多少呢?看起来,这些成员变量占用了偏移量从0~15的地址处,总大小应该是16,但是究竟是不是16呢?这就要看下一条规则:

  1. 结构体的总大小是其所有成员变量的最大对齐数的整数倍。

由于以上结构体的成员变量的对齐数分别是4、1、4,最大对齐数是4,而16就是4的整数倍,所以它的大小就是16。注意,这是一种巧合,如果16不是最大对齐数的整数倍,还要继续“浪费”空间,最终大小是最大对齐数的整数倍。

假设根据前面偏移量的计算,最后一个成员变量的偏移量是16~19,此时总体的偏移量是0~19,总大小是不是20呢?假设所有成员变量的最大对齐数是8,那么20就不是最终的大小,21也不是,22也不是,23也不是,24是8的整数倍,所以最终算出来的结构体大小是24。

如果有嵌套的结构体呢?

  1. 如果有嵌套的结构体,最终的大小是所有成员变量(包括嵌套的结构体的成员变量)的最大对齐数的整数倍。

如果有的成员变量是数组呢?

  1. 数组在内存中是连续存放的,只需要计算其首元素的偏移量即可。注意,数组的对齐数只是一个元素的对齐数,也就是说,只需要计算一个元素的大小和默认对齐数的较小值。

如何快速计算结构体的大小?

在一些笔试面试的问题中,会要求计算结构体的大小。此时我们的计算步骤是:

  1. 计算每个成员变量的对齐数(自身大小和默认对齐数的较小值)。
  2. 计算每个成员变量占用的地址的偏移量。
  3. 计算最终大小。

下面我再换一个结构体演示一下这几个步骤。

struct S
{
	char c1;
	double d;
	char c2;
	int i;
};

假设默认对齐数是8。

  1. 计算每个成员变量的对齐数(自身大小和默认对齐数的较小值),我用括号里的数表示该成员变量的对齐数。
struct S
{
	char c;     // (1)
	int i1;     // (4)
	double d;   // (8)
	int i2;     // (4)
	char ch[2]; // (1)
};
  1. 计算每个成员变量占用的地址的偏移量。我用中括号表示浪费的空间,用大括号表示占用的地址的偏移量。
struct S
{
	char c;     // (1) {0}
	int i1;     // (4) [1 2 3] {4 5 6 7}
	double d;   // (8) {8 9 10 11 12 13 14 15}
	int i2;     // (4) {16 17 18 19}
	char ch[2]; // (1) {20 21}
};
  1. 计算最终大小。

最大对齐数是8。从22往后数,直到数到8的倍数。22、23、24,停!没错,最终大小就是24。你学会了吗?

如何利用内存对齐节省结构体占用的内存空间?

观察到,只要把小的成员变量都放到一起,本来有可能会被浪费的空间就有可能被这些小的成员变量利用。感觉上,本来有些空间由于内存对齐的原因被浪费了,产生了空隙,而小的成员变量,就像沙子一样,可以填补这些空隙,这样就减少了空间的浪费,提高了空间的利用率。

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

主要有2个原因。

  1. 有些硬件只能访问对齐的位置的地址,如果没有内存对齐,可能有些数据是没办法访问的。
  2. 以空间换时间。如果没有内存对齐,可能需要访问2次才能读取一个数据,对齐之后,可能1次访问就能读取到数据,效率更高。举个例子:假设结构体内只有2个成员变量,分别是一个char数据和一个int数据,如果不对齐,char数据和int数据是挨着放的,只占用5个字节的空间,如果想要读取到int数据,假设一次只能读取4个字节,并且只能从对齐的位置(char数据所在的位置)开始读取,第一次只能读取到int数据的前3个字节,第二次读取才能读到int数据的最后一个字节,需要读取2次。但是如果对齐的话,就能直接从int数据的起始地址开始读,一次读取就能读取到整个int数据,效率就提升了,但是浪费了空间。所以说这是一种以空间换时间的策略。

如何修改默认对齐数?

可以用#pragma pack()来修改默认对齐数。比如:

#pragma pack(4)

以上代码就把默认对齐数修改为4。

#pragma pack()

以上代码,由于括号内没有数值,会把默认对齐数重置为编译器的默认值。

总结

  1. 结构体的内存对齐指的是,通过某些规则,“浪费”掉一定的空间,把每个成员变量的偏移量对齐到其对齐数的整数倍处。
  2. 可以通过3个步骤快速计算结构体大小,分别是:先计算每个成员变量的对齐数,再计算每个成员变量占用的地址的偏移量,最后计算整个结构体的大小。
  3. 把较小的成员变量放到一块,可以减小空间的浪费。
  4. 结构体的内存对齐是为了适应一些硬件,同时以空间换时间。
  5. 使用#pragma pack()来修改默认对齐数。

感谢大家的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式数据库架构路线大揭秘

文章目录分布式数据库是如何演进的?数据库与分布式中间件有什么区别?如何处理分布式事务,提供外部一致性?如何处理分布式SQL?如何实现分布式一致性?数据库更适合金融政企的未来这些年大家都在谈分布式数据库…

MySQL-中间件mycat(一)

目录 🍁mycat基础概念 🍁Mycat安装部署 🍃初始环境 🍃测试环境 🍃下载安装 🍃修改配置文件 🍃启动mycat 🍃测试连接 🦐博客主页:大虾好吃吗的博客 &#x1f9…

边缘网关thingsboard-gateway DTU902

thingsboard-gateway是一个采用python语言编写的开放源代码网关程序,用于将传统或第三方系统的设备与thingsboard平台连接。 支持 采集Modbus slaves、CAN、MQTT 、OPC-UA servers, Sigfox Backend。 除了具备普通 网关外,还具备可配置的边缘能力&…

rabbitmq深入实践

生产者,交换机,队列,消费者 交换机和队列通过 rounting key 绑定者,rounting key 可以是#.,*.这类topic模式, 生产者发送消息内容 rountingkey, 到达交换机后交换机检查与之绑定的队列, 如果能匹…

Yolov5之common.py文件解读

深度学习训练营原文链接前言0.导入需要的包以及基本配置1.基本组件1.1 autopad1.2 ConvDWConv模块1.3TransformerLayer模块1.4 Bottleneck和BottleneckCSPBottleneck模型结构1.5 CrossConv模块1.6 C3模块基于C3的改进1.7SPP1.8Focus模块1.9 Concat模块1.10 Contract和Expand1.1…

好东西!!!多亏几位大牛整理的面试题,让我成功上岸!!

凡事预则立,不预则废。相信很多程序员朋友在跳槽前都会临阵磨枪,在网络上搜集一些面试题进行准备。 然而,当机会来临时,却发现这些面试题往往“不快也不光”.... 由于Java面试涉及的范围很广,很杂,而且技…

使用MyBatis实现简单查询

文章目录一,创建数据库与表(一)在Navicat里创建MySQL数据库testdb(二)创建用户表 - t_user(三)在用户表里插入3条记录二,案例演示MyBatis基本使用(一)创建Mav…

解决idea每次打开新的项目都需要重新配置maven

原理:就是通过 idea 来进行全局配置【非当前工程配置】 IDEA 版本:2023.1 如何查看版本信息 ? 【主菜单】——【帮助】——【关于】 我在网上查找了许多文章 ,我混淆了一点!当前工程的设置 & 全局设置 不在一个地方…

马斯克掷重金收购英

人前主义,人后生意。在带领一众科技圈大佬签署了呼吁暂停研发比GPT-4更强AI模型的公开信后不久,马斯克却转头豪掷千金收购了10000块英伟达GPU。 一些网友吐槽,以马老板的格局而言,这次价值过亿的投资绝对不是为了借着AI概念火爆来…

2021年 团体程序设计天梯赛——题解集

Hello各位童学大家好!😊😊,茫茫题海你我相遇即是缘分呐,或许日复一日的刷题已经让你感到疲惫甚至厌倦了,但是我们真的真的已经达到了我们自身极限了吗?少一点自我感动,没有结果前别太…

[FREERTOS] 任务的创建、删除、调度与状态

1.什么是任务? 我的理解是:任务像是进程/线程,创建一个任务就会开辟一个空间,每一个任务都是独立的执行相应的动作互不干扰,就比如玩游戏,陪女朋友,任务通常都会有一个while(1)死循环 2.与任务创…

使用cloudflare代理flask启用https服务

原文来自:使用cloudflare代理flask启用https服务 | 夜空中最亮的星 欢迎大家留言讨论 问题1:使用cloudflare的dns回源服务器的时候,出现了http和https不断反复重定向 问题2: flask只能启用http服务,需要启用https 步骤 服务器&…

浅谈[Linux搭建GitLab私有仓库,并内网穿透实现公网访问]

转载自远控源码文章:Linux搭建GitLab私有仓库,并内网穿透实现公网访问 前言 GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。 Gitlab是被广泛使用的基于git的开源代码管理平…

报错解决:Python ‘NoneType‘ object is not subscriptable , 获取到的数据为None,需要保留数据

人生苦短,我用python 爬取某DB电影数据的时候, 在获取内容的时候出现 NoneType object is not subscriptablePython 资料报错交流:点击此处跳转文末名片获取 获取数据的部分代码是: writer_avatars (writers_list[wi][avatars][small]) …

Linux0.11 信号(十二)

系列文章目录 Linux 0.11启动过程分析(一) Linux 0.11 fork 函数(二) Linux0.11 缺页处理(三) Linux0.11 根文件系统挂载(四) Linux0.11 文件打开open函数(五&#xff09…

前端webpack项目性能优化——体积压缩和compression-webpack-plugin的使用

前端webpack项目性能优化——体积压缩和compression-webpack-plugin的使用需求优化结果需求 脚手架搭建的项目,会默认开启sourceMap,此时打包下来的包会很大,如图:map文件比所有js文件都大,会导致包整体体积过大&…

NIFI大数据进阶_Json内容转换为Hive支持的文本格式_操作方法说明_01_EvaluteJsonPath处理器---大数据之Nifi工作笔记0031

然后我们再来看一下如何把json内容,转换成hive支持的文本格式,其实还是一个格式转换对吧 首先看一下用到的处理器,可以看到这里我们用到了evaluateJsonPath处理器,这个处理器用来提取json中的熟悉,然后ReplaceText处理器用来替换掉FlowFile中的属性的内容 首先看一下这个Evalua…

【Python-Conda】Conda操作解读 pip 和 conda 的区别

【Python-Conda】Conda操作解读 & conda与pip的区别 文章目录【Python-Conda】Conda操作解读 & conda与pip的区别1. 介绍2. conda 操作2.1 创建环境2.2 查看conda已创建的环境2.3 删除环境2.3.1 删除虚拟环境中的包2.4 激活(失活)环境2.4.1 激活…

OpenAI Embedding:快速实现聊天机器人(三)

theme: orange 本文正在参加「金石计划」 接上文OpenAI Embedding:快速实现聊天机器人(二)有讲到聊天机器人的架构和流程,这篇开始通过代码讲讲具体实现。 前言 这篇文章为了降低实现的难度,下图中提供存储和向量相似度搜索的Redis(Redis Sea…

智媒ai在线伪原创-python文本自动伪原创

文章伪原创工具的优势 文章伪原创工具是一类自然语言处理工具,通过对原始文本进行语言转换、替换、重组等方式,生成与原始文本相似但不完全相同的新文本。这种工具的优势主要包括以下几点: 提高工作效率:使用文章伪原创工具可以快…