详解结构体(包含结构体内存对齐,柔性数组,位段)【尊嘟很详细】

结构体

结构体是一些值的集合,这些值称为成员变量,结构的成员可以是标量、数组、指针,甚至是其他结构体。

成员名可以与程序中其它变量同名,互不干扰。

结构体的定义

(struct+结构名+{})

struct books
{
	int a;
	char b;
	struct book* c;
};//分号不能少//

结构体成员不能在结构体内赋初值

关键字struct与结构名一起构成结构类型名

struct books是一个结构类型名

结构体可以嵌套定义

但结构体定义时***不允许***将成员的数据类型定义成自身的结构类型,这是因为结构类型的声明是构造阶段,系统还不知道需要分配多少内存空间。
但是结构类型中可以含有指向自身类型的指针变量。

结构变量

定义:

①在结构体定义时定义,此时变量位于结构体{}之后的;之前

在这里插入图片描述
②在结构体定义完后定义

在这里插入图片描述

③在匿名结构体定义时定义

在这里插入图片描述
由于此定义***省去了结构名***,在此定义语句后面无法再定义这个类型的其他结构变量,除非把定义过程再写一遍。

另外,以后如果再声明成员完全相同的结构类型,也和此次定义的结构类型属于不同的结构类型。

初始化:

对结构变量初始化时,需要按照其成员出现的顺序对每个成员依次赋值.
不能跳过前面的成员给后面的成员赋值

例如下面的用法是错误的:

struct books ps = { 1002,  ,p};

运算:

相同类型的结构体变量可以进行整体赋值,但***不能***进行关系运算

传参:

①传值:要重新拷贝一份结构体变量,空间和时间的浪费比较大。

②传址:只需要传4/8个字节,速度更快。

所以结构体传参的时候最好使用传址调用

用typedef给结构体命名

结构体的类型名是 struct+结构名,如果觉得它太长了,可以在定义时/定义完成后用typedef给结构体重命名。

重命名方法:

①非匿名结构体重命名

在这里插入图片描述

②匿名结构体重命名

在这里插入图片描述

结构体重命名之后就可以像定义int 类型变量一样,定义结构变量了

在这里插入图片描述
也可以在定义后给结构体重命名(此时只能给非匿名结构体重命名
给匿名结构体重命名会报错)
在这里插入图片描述

结构体的内存对齐(用干计算结构体的大小):

偏移量

是结构体变量的起始地址,向地址大的的增加量,如下图的柱形图的右侧的0.1.2.3.4.5.6.7.8.9等就是偏移量的值

在这里插入图片描述

结构体的对齐规则:

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

②其他成员变量要对齐到某个数字(对齐数)的整数倍的偏移量处。

对齐数=编译器默认对齐数与该成员自身字节中的***二者的较小值***。

有默认对齐数的编译器很少,常用的只有VS有默认对齐数,其值为8

默认对齐数也可以修改:

#pragma pack(数字)可以修改默认对齐数为括号中的数字

再次写#pragma pack()可以恢复成原默认对齐数

③结构体总大小为最大对齐数(每个成员变量都有一个对齐数,其中成员对齐数最大的成员的对齐数就是该结构体的最大对齐数)的整数倍。

④如果嵌套了结构体的情况,嵌套的结构体的第一个成员对齐嵌套结构体的最大偏移量的整数倍处
最外层结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

④如果成员是数组,则该成员的对齐数为它这个数组中元素类型的***字节数与编译器默认对齐数中较小的那一个。***

来算几个结构体的大小吧

struct books
{
	int a;
	char b;
	int c;
}sz;

a为第一个成员,所以它的第一个字节空间占据0偏移量,因为int类型占4个字节,所以还有3个字节的空间占据了1~3偏移量

因为除了第一个成员以外其他成员变量要对齐到其对齐数的整数倍的偏移量处
b的对齐数为1,因为4是1的倍数所以,char占据4偏移量处

c的对齐数为编译器默认对齐数与该成员自身字节中的二者的较小值,所以为4,
因为5~7不是4的整数倍,所以被舍弃,c的第一个字节空间占据第8个偏移量处,
剩下3个字节空间占据9~11偏移量。

该结构体的最大对齐数为4,0~11偏移量正好12个字节,因为12是4的整数倍,所以该结构体大小为12;

如图(啊,我图画的好丑):
在这里插入图片描述

struct books2
{
	char d;
	char e;
	double f;
};

struct books1
{
	int a;
	char b;
	struct books2 c;
};

a的对齐数为4,占据0-3偏移量,
b的对齐数为1,占据4偏移量
因为***嵌套的结构体的第一个成员对齐嵌套结构体的最大偏移量的整数倍处***
所以·先计算struct bools2的最大对齐数,为f的对齐数,值为8;
所以5-7偏移量舍去,
d占据8偏移量
e占据9偏移量
10-15没有8的倍数,所以舍弃
所以f占据16-23偏移量
结构体struct books1 的最大偏移量为struct books2中的f为8,因为0-23偏移量的字节数为24,为8的整数倍,所以struct books1的大小为24个字节

位段:

位段的定义:

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。
利用位段能够用较少的位数存储数据。

在这里插入图片描述

位段的特点:

①位段的成员可以是int ,unsigned int, signed int ,long(只要是整型就可以,浮点数不行)或者是char (属于整形家族)类型

②位段的 :后的值是 :前的位段成员所占的比特位(注意:位段中的变量所占比特位不能大于自身类型字节,例int类型的位段成员所占比特位不能超过32)

③位段的空间上是按照需要(根据类型)以4个字节(int) 或者1个字节( char )或者8个字节的方式来开辟的。不够用再按照需要(根据类型)以4个字节(int) 或者1个字节( char )开辟

struct books
{
	int _a:23;
	char _b:4;
	long _c: 22;
}A;

第一次遇到的类型为int所以开辟32个字节,这32个字节就可以囊括a,b了,因为c占22个字节而32-23-4=5<22,所以要再申请字节,因为遇到的是long所以再申请32个字节。

④位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

⑤位段只能存在于结构体中

位段中的截断

位段的变量赋值时

若位段的比特位小于赋值给位段的值的比特位时,会发生截断。

例:

struct books
{
	int _a:2;
	char _b:4;
	long _c: 2;
}A;
int main()
{
	A._c = 5;
	printf("%d \n", A._c);
	return 0;
}

_c的比特位为2,但是5的二进制位为101,所以去掉最高位1,存入了01,打印出来的就是1.
在这里插入图片描述

位段的跨平台问题:

1.类型为int位段成员(不写unsigned int/signed int)时被当成有符号数还是无符号数是不确定的。

2.位段中最大位的数目不能确定。(例如:long在Windows64位环境下占32个比特位,在Linux的64位环境下long却占64个比特位)

3.位段中的成员在内存中从左向右分配,还是从右向左分配(即存放数据时从左边的比特位开始存放,还是从右边的比特位开始存放)的c语言标准尚未定义。

4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时
例上面提到的:

struct books
{
	int _a:23;
	char _b:4;
	long _c: 22;
}A;

第一次遇到的类型为int所以开辟32个字节,这32个字节就可以囊括a,b了,因为c占22个字节而32-23-4=5<22,所以要再申请字节,因为遇到的是long所以再申请32个字节。
就有一个问题,32-23-4剩下的5个比特位是给c还是舍弃,这个问题的答案是不确定的[没有国际标准],所以不同平台可能不同。

位段总结:

跟结构体相比,位段可以达到同样的效果,是可以很好的节省空间,但是有跨平台的问题存在。

柔性数组:

定义:

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

特点:

①柔性数组只能存在于结构体中,且必须是结构体的最后一个成员

②结构中的柔性数组成员前面必须至少一个其他成员

③sizeof 返回的这种结构体大小不包括柔性数组的内存。

④包含柔性数组成员的结构体要用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小(即要给柔性数组前的成员动态内存分配)。

柔性数组的使用:

定义一个指针让其接受malloc的返回值,malloc的字节数为柔性数组前的成员的字节总数+给柔性数组的字节数

在这里插入图片描述
这样开辟的空间,也可以用realloc调整

有人就问了,为什么不能让struct books中的b设置成一个指针,然后让b动态内存申请呢?
其实是可以的,但是如果要保证***结构体中的所有成员都在堆区***,就要malloc两次
如下图:
在这里插入图片描述

柔性数组的好处

第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。
所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实, 我个人觉得也没多高了,反正你还是要用做偏移量的加法来寻址)

总结

柔性数组知道的人少不是没道理的,因为它确实没多大用,就算知道柔性数组的人也很少用它,

以上就是全部内容了,如果对你有帮助就点个赞支持一下吧!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最小覆盖子串(Java详解)

目录 一、题目描述 二、题解 一、题目描述 给定两个字符串 s 和 t 。返回 s 中包含 t 的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串&#xff0c;则返回空字符串 "" 。 如果 s 中存在多个符合条件的子字符串&#xff0c;返回任意一个。 注意&…

【IO】IO模型与零拷贝

前言&#xff1a; 正在运行的程序其实就是系统中的一个进程&#xff0c;操作系统会为每一个进程分配内存空间&#xff0c;而内存空间分为两部分&#xff0c;一部分是用户空间&#xff0c;这是用户进程访问的内存区域&#xff1b;另一部分是内核空间&#xff0c;是操作系统内核访…

详解Keras3.0 Layer API: LSTM layer

LSTM layer 用于实现长短时记忆网络&#xff0c;它的主要作用是对序列数据进行建模和预测。 遗忘门&#xff08;Forget Gate&#xff09;&#xff1a;根据当前输入和上一个时间步的隐藏状态&#xff0c;计算遗忘门的值。遗忘门的作用是控制哪些信息应该被遗忘&#xff0c;哪些…

vue2、vue3状态管理之vuex、pinia

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、状态管理之vuex1.1 State调用&#xff1a;1.2 Mutation在vuex中定义&#xff1a;在组件中使用&#xff1a; 1.3 Action在vuex中定义&#xff1a;将上面的减…

Vue 自定义ip地址输入组件

实现效果&#xff1a; 组件代码 <template><div class"ip-input flex flex-space-between flex-center-cz"><input type"text" v-model"value1" maxlength"3" ref"ip1" :placeholder"placeholder"…

VMware之FTP的简介以及搭建使用计算机端口的介绍

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《产品经理如何画泳道图&流程图》 ⛺️ 越努力 &#xff0c;越幸运 目录 一、FTP介绍 1、什么是FTP&#xff1a; 2、FTP适用于以下情况和应用场景&#xff1a; 3、winServer2012搭…

Verilog置换处理脚本

文章目录 一、介绍二、脚本 一、介绍 在Verilog中的置换处理&#xff0c;为将一个数据的数据位按照某种规则进行重新排列。 以DES算法的初始置换为例 初始置换将64比特的明文&#xff0c;按照初始置换表进行置换&#xff0c;得到一个乱序的64bit明文组。 初始置换表如下&…

加速计算,为何会成为 AI 时代的计算力“新宠”

随着科技的发展&#xff0c;处理大量数据和进行复杂计算的需求越来越高&#xff0c;人工智能、大数据和物联网等领域更是如此&#xff0c;传统的计算方式已经无法满足这些需求。因此&#xff0c;加速计算作为一种现代计算方式&#xff0c;成了必要的手段。加速计算具有前所未有…

为什么设计制造行业需要数据加密?

设计制造行业是一个涉及多种技术、工艺、材料和产品的广泛领域&#xff0c;它对经济和社会的发展有着重要的影响。然而&#xff0c;随着数字化、智能化和网络化的发展&#xff0c;设计制造行业也面临着越来越多的数据安全风险&#xff0c;如数据泄露、数据篡改、数据窃取等。这…

Qt Creator可视化交互界面exe快速入门4

上一期介绍了信号与槽&#xff0c;本期介绍加法计算器 我们来新建一个项目 然后拖动设置按钮 还需要个输出框 这里拖动Line Edit 我这里只是简单演示一下&#xff0c;做个低配版计算器&#xff0c;再加个加号和一个等于号就结束了。 然后回到代码编辑部分&#xff0c;我们需要…

代码随想录27期|Python|Day29|回溯算法|491.递增子序列|46.全排列|47.全排列 II

491. 非递减子序列 本题不是单纯的去重题目&#xff0c;而是需要保持数字在原数组的顺序。 比如&#xff1a;[4,5,6,7]和[4,6,5,7]相比&#xff0c;后者就不能选择[5,6,7]这个排列&#xff0c;因为违反了设置的顺序。所以去重的方法就只有哈希表。 需要在每一层设置一个哈希表…

注册谷歌企业开发者账号所需的邓白氏码是什么?如何获取?以及相关费用?

随着谷歌政策的收紧&#xff0c;谷歌对个人开发者账号发布应用的要求越来越高&#xff0c;需要20人连续测试14天&#xff0c;才能提审&#xff0c;因此很多开发者选择使用企业账号来进行上架。 而众所周知&#xff0c;注册谷歌企业开发者账号需要邓白氏码。什么是邓白氏码&…

制作系统U盘启动surface教程

最近本人是崩溃的&#xff1a;我surface pro9系统之前被我更新成win11 dev的开发预览版&#xff0c;不好用&#xff0c;有很多bug&#xff0c;升级后才发现已经是预览版成员资格&#xff0c;回退不了&#xff0c;重置初始化依然是dev预览版和取消预览计划是灰色的&#xff0c;退…

Volume Control 2

为游戏添加音乐和音效总是需要一些编码来设置一个系统来控制、显示和保存应用程序的音量设置。 音量控制的设计是为了立即为您设置这些内容,让您有更多时间专注于最重要的事情——制作出色的游戏! 在版本2中,我们对系统进行了重新设计,使其更加模块化、灵活,甚至更易于使用…

MySQL——索引

目录 一.没有索引&#xff0c;可能会有什么问题 二.MySQL与存储 1.先来研究一下磁盘 2.MySQL与磁盘交互基本单位 3.建立共识与总结 三.索引的理解 三.索引操作 1.创建主键索引 2.唯一索引的创建 3.普通索引的创建 4.全文索引的创建 四.查询索引 五.删除索引 一…

MySQL进阶SQL语句

1、select 显示表格种一个或数个字段的所有数据记录 语法&#xff1a;select "字段" from "表名"; 2、distinct 不显示重复的数据记录 语法&#xff1a;select distinct "字段" from "表名"; 3、where 有条件查询 语法&#x…

模式识别与机器学习-SVM(核方法)

SVM&#xff08;核方法&#xff09; 核方法核技巧在SVM中的应用 谨以此博客作为复习期间的记录 核方法 对解线性分类问题&#xff0c;线性分类支持向量机是一种非常有效的方法&#xff0e;但是&#xff0c;有时分类问题是非线性的&#xff0c;这时可以使用非线性支持向量机&a…

线程池原理及使用

线程池继承关系 1.为什么使用线程池&#xff1f; 1.反复创建线程开销大; 2.过多线程会占用太多内存(执行任务易出现“内存溢出”); 3.加快程序响应速度; 4.合理利用CPU和内存; 5.统一管理线程; 2.创建和停止线程池 2.1.线程池参数解释 1.keppAliveTime 如果线程池当中的线程数…

使用Python构建令人瞩目的高频交易算法

大家好&#xff0c;在金融领域&#xff0c;高频交易&#xff08;HFT&#xff09;因其能够以极高的速度执行大量订单的能力而备受关注。高频交易算法旨在识别并利用不同市场间的微小价格差异&#xff0c;因此交易者需要实现低延迟系统来进行套利策略&#xff0c;本文将探索使用P…

我的NPI项目之Android系统升级 - 同平台多产品的OTA

因为公司业务中涉及的面比较广泛&#xff0c;虽然都是提供移动终端PDA&#xff0c;但是使用的场景很多时候是不同的。例如&#xff0c;有提供给大型物流仓储的设备&#xff0c;对这样的设备必需具备扫码功能&#xff0c;键盘&#xff08;戴手套操作&#xff09;&#xff0c;耐用…