详解结构体内存对齐

news2024/11/24 7:16:02

目录

前言

一、结构体内存对齐规则

二、 offsetof 宏

三、结构体内存对齐的原因

四、 修改默认对齐数



前言

引入问题

#include <stdio.h>

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

int main()
{
	printf("%zd\n", sizeof(struct S));
	return 0;
}

程序运行后的结果是:12,这表明 sizeof(struct S) 并不是简单地等于 sizeof(char) + sizeof(int) + sizeof(char)。

那么如何准确地计算出一个结构体的大小呢?

 

一、结构体内存对齐规则

要准确地计算出结构体的大小,就需要掌握结构体内存对齐的规则

结构体成员是按照定义顺序一个一个地放到内存中的,但并不一定是紧密排列的

  1. 第一个成员的首地址在与结构体变量偏移量为 0 的地址处。

  2. 其他成员的首地址在对齐数的整数倍的地址处。

  3. 结构体的总大小为最大对齐数(每个成员都有一个对齐数)的整数倍。

  4. 如果嵌套了结构体的情况,嵌套的结构体成员的首地址在自己的最大对齐数的整数倍的地址处,结构体的大小就是所有最大对齐数(含嵌套结构体成员的对齐数)的整数倍。

对齐数 = 编译器默认的一个对齐数与该成员大小的较小值

不是所有的编译器都有默认对齐数,VS 中的默认对齐数为 8,Linux GCC 则没有默认对齐数,当编译器没默认对齐数时,成员变量的大小就是该成员的对齐数

例如

#include <stdio.h>

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

struct S2
{
	char c1;
	struct S1 s1;
	double d;
};

int main()
{
	printf("%zd\n", sizeof(struct S1));
	printf("%zd\n", sizeof(struct S2));
	return 0;
}

分析: 

 所以 sizeof(struct S1) == 16。

 

 所以 sizeof(struct S2) == 32。

二、 offsetof 宏

可以使用 offsetof 宏计算一个结构体成员的偏移量

例如

#include <stdio.h>
#include <stddef.h>
struct S
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%zd\n", offsetof(struct S, c1));  // 0
	printf("%zd\n", offsetof(struct S, i));  // 4
	printf("%zd\n", offsetof(struct S, c2));  // 8
	return 0;
}

模拟实现

#define OFFSETOF(sType, member) (size_t)&(((sType*)0)->member)
  1. 首先将 0 强制类型转换成结构体指针,然后就可以使用 -> 操作符访问其中的成员。

  2. 对指向的成员取地址 &,该地址就是偏移量,因为偏移量 = 成员地址 - 起始地址(0) = 成员地址

  3. 将结果强制类型转换成 size_t

三、结构体内存对齐的原因

  1. 平台原因(移植原因)

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

  2. 性能原因

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

    假如没有内存对齐机制,数据可以任意存放。现在一个 int 类型的变量存放在地址 1 开始的连续四个字节中,处理器去取数据时,要先从 0 地址开始读取第一个 4 字节块,剔除不想要的字节(0 地址),然后从地址 4 开始读取下一个 4 字节块,同样剔除不想要的字节(5、6、7 地址),最后留下的两个数据合并放入寄存器,这需要做很多工作。

    现在有了内存对齐,int 类型的数据只能存放在按照对齐规则的内存中,比如说 0 地址开始的内存,那么现在处理器在取数据时一次性就能将数据读出来,而不需要做额外的操作,提高了效率。

四、 修改默认对齐数

使用 #pragma 预处理指令修改默认对齐数

例如

#include <stdio.h>

#pragma pack(2)  // 设置默认对齐数为 2
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()  // 取消设置的默认对齐数,还原为默认

int main()
{
	printf("%zd\n", sizeof(struct S));  // 8
	return 0;
}

没有修改默认对齐数时,sizeof(struct S) 等于 12;而将默认对齐数修改为 2 后,sizeof(struct S) 就变成了 8

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

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

相关文章

干货 | 人脸识别技术的风险及应对方案

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;人脸识别技术概述人脸识别的发展阶段&#xff0c;主要分为三个阶段&#xff1a;起步阶段&#xff08;1950s-1980s&#xff09;&#xff0c;这一阶段的人脸识别只是作为一般性…

房产管理系统---系统安全性需求分析

数图互通高校房产管理系统是基于公司自主研发的FMCenterV5.0平台&#xff0c;是针对中国高校房产的管理特点和管理要求&#xff0c;研发的一套标准产品&#xff1b;通过在中国100多所高校的成功实施和迭代&#xff0c;形成了一套成熟、完善、全生命周期的房屋资源管理解决方案。…

Linux学习笔记——HBase集群安装部署

5.11、大数据NoSQL数据库HBase集群部署 5.11.1、简介 HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。 和Redis一样&#xff0c;HBase是一款KeyValue型存储的数据库。 不过和Redis设计方向不同&#xff1a; Redis设计为少量数据&#xff0c;超快检索HBase设计…

【部署】Docker容器

Docker 使用 Google 公司推出的 Go 语言进行开发实现&#xff0c;基于 Linux 内核的 cgroup、namespace 以及 OverlayFS 类的 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程&#xff0…

算法刷题打卡第63天:对称二叉树

对称二叉树 难度&#xff1a;简单 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false…

BOM浏览器对象模型

文章目录一、BOM概述1、什么是BOM2、BOM的构成二、window 对象的常见事件1、窗口加载事件&#xff08;1&#xff09;window.onload&#xff08;3&#xff09;DOMContentLoaded2、调整窗口大小事件三、定时器1、两种定时器2、setTimeout()定时器3、停止 setTimeout() 定时器4、s…

如何使用CMD修复硬盘命令来解决硬盘问题?

随着计算机的越来越普及&#xff0c;现在在我们的日常生活中都会使用到计算机电脑。硬盘作为计算机电脑的主要存储设备&#xff0c;里面存储着我们平时使用的软件文件、文档资料、照片等重要的数据文件。一旦硬盘损坏会给我们带来许多不必要的麻烦&#xff0c;那硬盘损坏有哪些…

图解卡尔曼滤波(Kalman Filter)

背景关于滤波首先援引来自知乎大神的解释。“一位专业课的教授给我们上课的时候&#xff0c;曾谈到&#xff1a;filtering is weighting&#xff08;滤波即加权&#xff09;。滤波的作用就是给不同的信号分量不同的权重。最简单的loss pass filter&#xff0c; 就是直接把低频的…

【Linux操作系统】1. Linux操作系统简介、安装

前言 本系列是Linux操作系统的一些知识以及实践内容&#xff0c;Linux操作系统作为开发最常使用的操作系统&#xff0c;是必备的一门求职、提升技术。本文先介绍Linux操作系统&#xff0c;并安装一个Linux操作系统。 Linux操作系统简介 Linux&#xff0c;全称GNU/Linux&#…

Javadoc

Javadoc 在学习JavaSE时&#xff0c;我们知道Java支持三种注释方式&#xff1a; 单行注释多行注释文档注释 Javadoc是文档注释&#xff0c;用来对类或方法进行标准的注释&#xff0c;在开发中写好JavaDoc非常重要。 在调用方法时&#xff0c;你可能会看到这样的情景 这种注…

Unity - 搬砖日志 - 如何设置AssetDatabase.Create(“xxx.asset“, mesh) 的Read/Write=false

最近很忙&#xff0c;想写的 BLOG 都遗漏编写了 踩坑的时间比较多&#xff0c;充电的时间少了很多 为了减少以后自己填坑时间&#xff0c;随便简单的记录一下 搬砖日志 环境 unity : 2020.3.37f1 pipeline : brp 问题 因为之前搜索、购买、使用了各式各样的 LOD 插件、工具…

机器学习100天(三十一):031 K近邻回归算法

机器学习100天,今天讲的是:K 近邻回归算法! 《机器学习100天》完整目录:目录 一、理论介绍 我们之前讲了 K 近邻分类算法,用来处理分类问题。其实 K 近邻也可以用来处理回归问题。 如左图所示,K 近邻分类算法的思路是选取与测试样本距离最近的前 k 个训练样本。然后对…

回收租赁商城系统功能拆解07讲-订单列表

回收租赁系统适用于物品回收、物品租赁、二手买卖交易等三大场景。 可以快速帮助企业搭建类似闲鱼回收/爱回收/爱租机/人人租等回收租赁商城。 回收租赁系统支持智能评估回收价格&#xff0c;后台调整最终回收价&#xff0c;用户同意回收后系统即刻放款&#xff0c;用户微信零…

寒假题练——day(3)

题目1&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 进阶&#xff1a;尝试设计时间复杂度为 O(n)、空间复杂度为…

58、正则表达式

目录 一、快速入门 二、正则表达式基本语法 1、基本介绍&#xff1a; 2、正则表达式底层实现 3、元字符&#xff08;Metacharacter&#xff09;- 转义号\\ &#xff08;1&#xff09;限定符 &#xff08;2&#xff09;选择匹配符 &#xff08;5&#xff09;字符匹配符…

FX5U DRVMUL指令多个轴的表格运行

1.简述该指令可以用GX Works3预先在表格数据中设定的控制方式的动作&#xff0c;执行多个轴的表格。 2.指令解释同时执行多个轴的表格。指令执行开始后&#xff0c;各轴独立进行动作&#xff0c;也可连续运行。但是&#xff0c;只可在同一模块内同时执行2.1 操作数n1在(n1)中指…

.NETCore 多线程学习笔记(多线程、线程结束顺序掌控、线程相对平均分配)

参考资料&#xff1a;多线程MutiThread最佳实践专题-1-1_哔哩哔哩_bilibili 跟着视频学习的&#xff0c;下面是自己的注释笔记和实验结果 写了个窗体来学习线程的 多线程、线程掌控、线程分配 下面会用到这个方法 /// <summary> /// 仅仅只是一个耗时方法 …

STM32RTC外设详解

目录一.RTC 实时时钟简介1.RTC时钟来源2.RTC主要特性二.RTC 外设功能框图1.RTC功能框图剖析2.使能对后备寄存器和RTC的访问3.复位过程4.读RTC寄存器5.配置RTC寄存器三.实现一个简易时钟1.实验目的2.实验原理3.实验源码4.效果演示一.RTC 实时时钟简介 实时时钟是一个独立的定时…

链表题目总结 -- 双指针技巧

文章目录一. 合并两个有序链表1. 思路简述2. 代码3. 总结二. 分隔链表1. 思路简述2. 代码3. 总结三. 合并K个升序链表1. 思路简述2. 代码3. 总结四. 单链表的倒数第 k 个节点1. 思路简述2. 代码3. 总结五. 链表的中间结点1. 思路简述2. 代码3. 总结六. 环形链表&#xff08;链表…

docker一个容器内部署多个服务

原因是&#xff0c;我有一个springBoot服务需要写入httpd的目录&#xff0c;然后httpd提供链接给别人下载。之前的方法是&#xff0c;httpd和springBoot各一个容器&#xff0c;但是我们将镜像是部署在腾讯云上的&#xff0c;腾讯云会自动对每个容器分离不同的虚拟机&#xff0c…