【C语言】自定义类型:结构体深入解析(二)结构体内存对齐宏offsetof计算偏移量结构体传参

请添加图片描述

文章目录

  • 📝前言
  • 🌠 结构体内存对齐
  • 🌉内存对齐包含结构体的计算
  • 🌠宏offsetof计算偏移量
  • 🌉为什么存在内存对⻬?
  • 🌠 结构体传参
  • 🚩总结


📝前言

本小节,我们学习结构的内存对齐,理解其对齐规则,内存对齐包含结构体的计算,使用宏offsetof计算偏移量,为什么要存在内存对齐?最后了解结构体的传参文章干货满满!学习起来吧😃!

🌠 结构体内存对齐

结构体内存对齐指的是结构体中各成员变量在内存中的存储位置按照一定规则对齐
既然是按照一定规则,那得首先了解它的对齐规则:

  1. 结构体的第一个成员对齐到和结构体起始位置偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值
  • VS 中默认的值为 8
  • linuxgcc没有默认对齐数,对齐数就是成员自身的大小
  1. 结构体总大小为最大对齐数(结构体中的每一个成员都有一个对齐数,所有对齐数中的)的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
  • 来代码理解:
struct S1
{char c1;char c2;int i;
};struct S2
{char c1;int i;char c2;
};int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0;
}

代码运行:
在这里插入图片描述

分析:
在这里插入图片描述
首先结构体S1的成员有三个,根据对齐规则:结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处-—>C1放在偏移量为0的地址处,接下来第二个C2就从第2个规则按对齐数进行放置,C2的字节数char类型,大小为1VS的默认对齐数为8,对齐数取的是默认对齐数和成员变量字节大小的较小值,1<8,取1为对齐数,然后偏移量为1的位置放1,此时再看第三个变量i的字节大小为44<8,对齐数为4,当放在偏移量为2时,2不是4的整数倍,跳过,3也不是,跳过,而当偏移量为4时刚好是4的整数1倍(4*1=4),然后占据为4个字节空间,从偏移量0到最后偏移量的空间就是结构体的总大小,为8,此时还没有结束,要验证,根据第三条规则结构体的总大小为最大对齐数的整数倍,最大对齐数为44>1>1),而结构刚才计算出来是8刚好是4整数倍(4*2)当这些都符合了,结构体的大小就是8了。

一个例子你可能想是不是碰巧,那么第二个例子:
结构体S2中有三个成员,C1大小为一,第一个成员放在偏移量为0处,第二个成员i大小为4,偏移量123都不是4的整数倍,然后这些空间都跳过不放数据,(注:他开辟了空间,但他此时不用,你可能会想:这不浪费吗?文章我们慢慢解释)然后偏移量为4时为整数倍,从偏移量4开始放i直到7,第三个元素C2大小为11的整数倍任何数的整数倍,可以直接放,当放在偏移量8处时,全部成员都放完了,我们还要对他进行验证是否为整数倍S2最大对齐数是4,偏移量9,10都不对,当偏移量为11,从011刚好为12,为4的倍数(4*3=12)。所以S2总大小为12

🌉内存对齐包含结构体的计算

struct S3
{double d;char c;int i;
};
int main()
{printf("%zd\n", sizeof(struct S3));return 0;
}
运行结果:16

分析:

在这里插入图片描述
首先第一个成员为d,放在偏移量为0处,double类型,大小为8,位置范围为0 ~ 7,第二个成员C ,类型为char,大小为11<8,对齐数为1,1可以直接放,占据8位置处,第三个成员i,大小为4,4<8,对齐数是4,偏移量9,10,11都不是4的倍数,12开始占据4个空间到15,范围0 ~ 15总大小为16。S3结构体是三个成员(8>4>1)大小最大是double大小为8,此时总结构体大小16刚好为82倍,符合条件。

  • [] 包含S3的结构体
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S4));return 0;
}
运行结果:32

第一个成员C1对应到偏移量为0处,大小为1s3为结构体,s3的大小为16,根据第四条规则【如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。】,也就是说结构体s3最大对齐数为double的8,用8对齐到S4中整数倍,1,2,3,4,5,6,7都不是8的整数倍,跳过,当偏移量为8时为对齐数8的整数倍时,然后结构体整体大小为16,占据范围为8 ~ 23,接下来就是第三个元素d,大小为8,偏移量24就是8的整数倍,占据了24 ~ 31,所有成员都完成了,偏移量范围在0 ~ 31,总大小就是32。答案就是32.看到这里的你,给自己鼓个掌,继续加油。

在这里插入图片描述

🌠宏offsetof计算偏移量

宏offsetof可以用来计算结构体成员相对于结构体起始位置的偏移量。
宏offsetof原型:

offsetof(type, member)
type是结构体类型
member是结构体中的成员。

注意:使用offsetof宏计算结构体成员偏移量时,需要包含stddef.h头文件

# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stddef.h>
struct S1
{char c1;char c2;int i;
};struct S2
{char c1;int i;char c2;
};struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{struct S1 s1 = {0};//8struct S2 s2 = { 0 };//12printf("结构体大小:\n");printf("S1=%zd\n", sizeof(struct S1));//8printf("S2=%zd\n", sizeof(struct S2));//12printf("S3=%zd\n", sizeof(struct S3));//16printf("S4=%zd\n", sizeof(struct S4));//32printf("\n"); printf("结构体S1成员的偏移量:\n");printf("c1=%zd\n", offsetof(struct S1, c1));//0printf("c2=%zd\n", offsetof(struct S1, c2));//1printf(" i=%zd\n", offsetof(struct S1, i));//8printf("\n");printf("结构体S2成员的偏移量:\n");printf("c1=%zd\n", offsetof(struct S2, c1));//0printf(" i=%zd\n", offsetof(struct S2, i));//4printf("c2=%zd\n", offsetof(struct S2, c2));//8printf("\n");printf("结构体S4成员的偏移量:\n");printf("c1=%zd\n", offsetof(struct S4, c1));//0printf("s3=%zd\n", offsetof(struct S4, s3));//8printf("d=%zd\n", offsetof(struct S4, d));//24return 0;
}

运行+图对比:
在这里插入图片描述

🌉为什么存在内存对⻬?

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

假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果数据没有对齐,CPU需要额外的时间来处理非对齐的内存访问,这会降低性能。
在这里插入图片描述

总结一句话来说:
结构体的内存对⻬是拿空间来换取时间的做法。

在设计结构体时,既要满足内存对齐要求,又要考虑节省空间,可以采取以下方法:

  • 尽量将较小类型如charshort等成员放在结构体开始位置。这可以减少由对齐产生的内存浪费。
    例如前面的S1S2就很典型:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};

阿森把宝图解:
在这里插入图片描述

  • 修改默认对⻬数
    #pragma 这个预处理指令,可以改变编译器的默认对⻬数。
    #pragma 原型:
#pragma pack(push, 1) // 将结构体对齐数设置为1字节 
struct S1
{char a; int b;
};
#pragma pack(pop)// 恢复之前的对齐数
  • pack(push, 1)表示将当前对齐数压入栈,并设置新的对齐数为1字节
  • pack(pop)表示从栈中弹出之前的对齐数,恢复默认对齐数

可以直接指定对齐数:

#pragma pack(1) 
struct S1
{				// 成员对齐数为1字节char a; int b;
};#pragma pack() // 恢复默认对齐数

例子:

#pragma pack(1)
struct S1
{char c1;char c2;int i;
};
#pragma pack()int main()
{printf("%d\n", sizeof(struct S1));return 0;
}

输出:
在这里插入图片描述
图解对比:
在这里插入图片描述

🌠 结构体传参

  1. 按值传递(传结构体)
    函数形参声明为结构体,实参传递结构体变量。此时在函数内对形参的修改不会影响实参。
struct St 
{int x;
};void func(struct St st) 
{st.x = 10;
}int main() 
{struct St s = { 0 };func(s);//传结构体printf("%d\n", s.x);
}

输出:
在这里插入图片描述

  1. 按地址传递
    函数形参定义为结构体指针,实参传递结构体变量的地址。函数内对形参所指结构体的修改会影响实参。
struct St 
{int x;
};void func(struct St* p) 
{p->x = 10;
}int main() {struct St s = { 0 };func(&s);printf("%d\n", s.x);
}

输出:
在这里插入图片描述

  1. 传结构体指针
    实参直接传结构体指针:
struct St 
{int x;
};void func(struct St* st) 
{st->x = 10;
}int main() 
{struct St s;struct St* p = &s;func(p);printf("%d\n", s.x);
}

输出:10

分析:
传值也就是把整个结构体传过去,我们知道形参是是实参的一份临时拷贝,需要再创建特别大的空间来存储结构体。
在这里插入图片描述

无论是传结构体指针还是传结构体地址,本质上都是传地址,但是传地址,只需要创建一个小的空间来存储地址。
在这里插入图片描述

选择传地址比较好一些。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。

总结:
结构体传参的时候,要传结构体的地址。


🚩总结

这次阿森和你一起学习结构体的 结构体内存对齐,内存对齐包含结构体的计算,使用宏offsetof计算偏移量,为什么存在内存对⻬? 结构体传参的本质,阿森将下一节和你一起学习结构体实现位段。

感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++面向对象(OOP)编程-STL详解(vector)

本文主要介绍STL六大组件&#xff0c;并主要介绍一些容器的使用。 目录 1 泛型编程 2 CSTL 3 STL 六大组件 4 容器 4.1 顺序性容器 4.1.1 顺序性容器的使用场景 4.2 关联式容器 4.2.1 关联式容器的使用场景 4.3 容器适配器 4.3.1 容器适配器的使用场景 5 具体容器的…

大模型ChatGLM下载、安装与使用

在人工智能领域&#xff0c;清华技术成果转化的公司智谱AI启动了支持中英双语的对话机器人ChatGLM内测。ChatGLM是一个初具问答和对话功能的千亿中英语言模型&#xff0c; 并针对中文进行了优化&#xff0c;现已开启邀请制内测&#xff0c;后续还会逐步扩大内测范围。 ChatGLM…

Unity中Shader平移矩阵

文章目录 前言方式一&#xff1a;对顶点本地空间下的坐标进行相加平移1、在属性面板定义一个四维变量记录在 xyz 上平移多少。2、在常量缓冲区进行申明3、在顶点着色器中&#xff0c;在进行其他坐标转化之前&#xff0c;对模型顶点本地空间下的坐标进行转化4、我们来看看效果 方…

Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)

文章目录 Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)1、正确的运行页面2、报错404问题分类解决2.1、Tomcat未配置环境变量2.2、IIs访问权限问题2.3、端口占用问题2.4、文件缺少问题解决办法&#xff1a; Tomcat报404问题解决方案大全(包括tomcat可以正常运…

智能优化算法应用:基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.龙格-库塔算法4.实验参数设定5.算法结果…

@vue/cli脚手架

0_vue/cli 脚手架介绍 目标: webpack自己配置环境很麻烦, 下载vue/cli包,用vue命令创建脚手架项目 vue/cli是Vue官方提供的一个全局模块包(得到vue命令), 此包用于创建脚手架项目 脚手架是为了保证各施工过程顺利进行而搭设的工作平 vue/cli的好处 开箱即用 0配置webpack babe…

算法模板之栈图文详解

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;算法模板、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️模拟栈1.1 &#x1f514;用数组模拟实现栈1.1.1 &#x1f47b;栈的定义1.1.…

SQL---Zeppeline前驱记录与后驱记录查询

内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…

JMeter常见配置及常见问题修改

一、设置JMeter默认打开字体 1、进入安装目录&#xff1a;apache-jmeter-x.x.x\bin\ 2、找到 jmeter.properties&#xff0c;打开。 3、搜索“ languageen ”&#xff0c;前面带有“#”号.。 4、去除“#”号&#xff0c;并修改为&#xff1a;languagezh_CN 或 直接新增一行&…

Zookeeper集群搭建,四字命令监控,Leader选举原理以及数据如何同步

Java学习面试指南&#xff1a;https://javaxiaobear.cn 1、集群角色 Leader&#xff1a; 领导者。 事务请求&#xff08;写操作&#xff09;的唯一调度者和处理者&#xff0c;保证集群事务处理的顺序性&#xff1b;集群内部各个服务器的调度者。对于create、setData、delete…

汽车制造厂设备故障预测与健康管理PHM

在现代汽车制造工业中&#xff0c;设备的可靠性和稳定性对于保证生产线的高效运行至关重要。为了提高生产效率、降低维修成本以及确保产品质量&#xff0c;汽车制造厂逐渐采用设备故障预测与健康管理&#xff08;PHM&#xff09;系统&#xff0c;以实现对设备状态的实时监测和预…

[数据结构进阶 C++] 二叉搜索树(BinarySearchTree)的模拟实现

文章目录 1、二叉搜索树1.1 二叉搜索数的概念1.2 二叉搜索树的操作1.2.1 二叉搜索树的查找1.2.2 二叉搜索树的插入1.2.3 二叉搜索树的删除 2、二叉搜索树的应用2.1 K模型2.2 KV模型 3、二叉搜索树的性能分析4、K模型与KV模型完整代码4.1 二叉搜索树的模拟实现&#xff08;K模型…

【Java】编写一个简单的Servlet程序

Java Servlet 是运行在 Web 服务器或应用服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 使用 Servlet&#xff0c;可以收集来自网页表单的用户输入&#xff0c;呈现来自数据库或者其他源的记录…

求交错序列前N项和 C语言xdoj149

题目描述&#xff1a;编写程序&#xff0c;计算交错序列1-2/33/5-4/75/9-6/11…的前N项之和。 输入格式&#xff1a;输入一个正整数 输出格式&#xff1a;输出计算结果&#xff0c;结果保留三位小数 示例&#xff1a; 输入&#xff1a;5 输出&#xff1a;0.917 #include <st…

基于深度学习的森林火焰烟雾检测系统(含UI界面,yolov8、Python代码,数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8 yolov8主要包含以下几种创新&#xff1a;         1. 添加注意力机制&#xff08;SE、CBAM等&#xff09;         2. 修改可变形卷积&#xff08;DySnake-主干c…

二分查找法详解(6种变形)

前言 在之前的博客中&#xff0c;我给大家介绍了最基础的二分查找法&#xff08;没学的话点我点我&#xff01;&#xff09; 今天我将带大家学习二分法的六种变形如何使用&#xff0c;小伙伴们&#xff0c;快来开始今天的学习吧&#xff01; 文章目录 1&#xff0c;查找第一个…

Ubuntu 常用命令之 du 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 Ubuntu系统下的du命令是一个用来估计和显示文件和目录所占用的磁盘空间的命令。du是“disk usage”的缩写&#xff0c;这个命令可以帮助用户了解磁盘被哪些文件和目录使用。 du命令的常见参数有 -a&#xff1a;列出所有文件和目…

Python实验报告十一、自定义类模拟三维向量及其运算

一、实验目的&#xff1a; 1、了解如何定义一个类。 2、了解如何定义类的私有数据成员和成员方法。 3、了解如何使用自定义类实例化对象。 二、实验内容&#xff1a; 定义一个三维向量类&#xff0c;并定义相应的特殊方法实现两个该类对象之间的加、减运算&#xff08;要…

【数据结构和算法】最大连续1的个数 III

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一&#xff1a;滑动窗口 2.2 滑动窗口解题模板 三、代码 3.1 方法一&#xff1a;滑动窗口 四、…

Echarts 仪表盘实现平均值和实时值

const gaugeData [{value: 20,name: 互动发起率实时值,title: {offsetCenter: [-25%, 10%]},detail: {offsetCenter: [-25%, 18%]}},{value: 40,name: 互动发起平均值,title: {offsetCenter: [25%, 10%]},detail: {offsetCenter: [25%, 18%]}},// {// value: 60,// name: …