C语言第三十一弹---自定义类型:结构体(下)

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】

目录

1、结构体内存对齐

1.1、为什么存在内存对齐?

1.2、修改默认对齐数

2、结构体传参

3、结构体实现位段

3.1、什么是位段

3.2、位段的内存分配

3.3、位段的跨平台问题

3.4、位段的应用

3.5、位段使用的注意事项

总结


上一弹我们讲解了结构体内存对齐的规则,那为什么我们需要内存对齐呢?

我们通过这一弹来进行讲解。

1、结构体内存对齐

1.1、为什么存在内存对齐?

大部分的参考资料都是这样说的:

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

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

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在⼀起

//例如:
struct S1{char c1;int i;char c2;};struct S2{char c1;char c2;int i;};

S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的大小有了⼀些区别。

很显然,根据上一弹讲解的内存对齐规则,S1所占的空间更大。

1.2、修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#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;
}

根据上一弹结构体内存对齐规则,可知该结构体的大小如上图,即6字节。

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

2、结构体传参

struct S
{int data[1000];int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}

上面的 print1 和 print2 函数哪个好些?

答案是:首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论:

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

3、结构体实现位段

结构体讲完就得讲讲结构体实现 位段 的能力。

3.1、什么是位段

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

位段的声明和结构是类似的,有两个不同:

1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以

选择其他类型。

2. 位段的成员名后边有⼀个冒号和⼀个数字。

比如:

struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};

A就是⼀个位段类型。

那里面的冒号和数字又表示什么呢?

首先我们要明白位段中的这个“位”字其实指的是二进制位。
我们知道一个二进制位就是1个比特位。
所以,A中int _a:2; 其实表示的就是_a的大小是2bit;
同理:
_b的大小是5bit
_c的大小是10bit
_d的大小是30bit

知道各自占用的内存空间之后,是不是将内存空间相加就是该结构体的真实内存大小呢?

2+5+10+30=47bit  因为一字节等于8bit,47bit接近6字节,是不是结构体空间就是6字节?我们通过VS测试来揭晓一下答案。

printf("%d\n", sizeof(struct A));

此处uu们肯定疑惑,刚刚算出来还不到6字节,为什么答案就是8字节呢?接下来我们来进行深入讲解。

3.2、位段的内存分配

从上面的讲解我们知道:

1. 位段的成员可以是 int、unsigned int、signed int 或者是 char 等类型

那位段所需的空间是如何分配的呢?

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

此句话该怎么理解呢?

就是说,如果位段的成员全部是整型的(位段成员一般都是同类型的),那上去就先给这个位段开辟4个字节的空间,如果不够用,放不下所有的成员,那就再开辟4个字节的空间,还不够用,继续开辟,以此类推。如果成员全部是char类型的,那就一次开辟1个字节的空间,直至放得下所有成员。

注意:

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

我们知道这些位段的规则之后,那么上面的struct A的内存大小是如何计算出来的呢?

根据上述规则可知,位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的,此处为int类型字段,因此需要先开辟4字节空间,但是此处是小端存储还是大端存储呢?我们前面讲解的VS中整数在内存中的存储可知,VS采用小端存储,那么此处我们也采用小端存储来计算大小,先占用2bit空间,如上图绿色方框再占用5bit空间,如上图橙色方框,再占用10bit空间,但是此处第一个字节只剩1bit空间了,空间不够,需要占用新的空间,如上图蓝色方框,但是,最后占用30bit,同理空间不够需要占用新的空间,先占用8bit,但是此处已经没有空间了,所以需要再创建4字节空间,然后按照上图存放在内存空间中,如上图紫色方框。因此该结构体内存大小为8字节。

我们虽然根据上面的规则和VS整数在内存中存储得出上面结果,但是就一定是这样?下面我们再用一个例子证明一下。

//一个例子
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;//空间是如何开辟的?return 0;
}

根据上述结构体字段的规则,此处内存的占用如上图。

此时还是证明不了内存中真实如何存储,此时我们可以赋值一些值进行验证。

现在通过调试看看结构体s在内存中是如何存储的。

根据VS编译器的调试可知,结构体位段在VS中是按照小端进行存储的,但是不是所有编译器都是这样,因此用到其他编译器时需要自行证明。

3.3、位段的跨平台问题

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:

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

3.4、位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对网络的畅通是有帮助的。

3.5、位段使用的注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在⼀个变量中,然后赋值给位段的成员。

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A sa = {0};scanf("%d", &sa._b);//这是错误的//正确的⽰范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

论文精读--GPT3

不像GPT2一样追求zero-shot&#xff0c;而换成了few-shot Abstract Recent work has demonstrated substantial gains on many NLP tasks and benchmarks by pre-training on a large corpus of text followed by fine-tuning on a specific task. While typically task-agnos…

EMQX Enterprise 5.5 发布:新增 Elasticsearch 数据集成

EMQX Enterprise 5.5.0 版本已正式发布&#xff01; 在这个版本中&#xff0c;我们引入了一系列新的功能和改进&#xff0c;包括对 Elasticsearch 的集成、Apache IoTDB 和 OpenTSDB 数据集成优化、授权缓存支持排除主题等功能。此外&#xff0c;新版本还进行了多项改进以及 B…

多模态表征—CLIP及中文版Chinese-CLIP:理论讲解、代码微调与论文阅读

我之前一直在使用CLIP/Chinese-CLIP&#xff0c;但并未进行过系统的疏导。这次正好可以详细解释一下。相比于CLIP模型&#xff0c;Chinese-CLIP更适合我们的应用和微调&#xff0c;因为原始的CLIP模型只支持英文&#xff0c;对于我们的中文应用来说不够友好。Chinese-CLIP很好地…

unity发布webGL压缩方式的gzip,使用nginx作为web服务器时的配置文件

unity发布webGL压缩方式的gzip&#xff0c;使用nginx作为web服务器时的配置文件 Unity版本是&#xff1a;2021.3 nginx的版本是&#xff1a;nginx-1.25.4 Unity发布webgl时的测试 设置压缩方式是gzip nginx配置文件 worker_processes 1;events {worker_connections 102…

Linux系统——Nginx小总结

目录 一、影响用户体验的因素 二、网络连接——Apache/Nginx服务请求过程 三、I/O模型——Input/Output模型 1.同步/异步 2.阻塞/非阻塞 3.同步异步/阻塞非阻塞组合 四、Nginx用法 一、影响用户体验的因素 客户端硬件配置客户端网络速率客户端与服务端距离服务端网络速…

SQLlabs46关

看看源码 最终我们的id是放到order by后面了 如果我们直接用列去排序 ?sortusername/password username&#xff1a; passward 可以看到顺序是不同的&#xff0c;当然第一列第二列第三列也可以&#xff0c;基本上都是这个原理&#xff0c;那怎么去实现注入呢&#xff0c;我…

Rider 2023:打造高效.NET项目的智能IDE,让开发更简单mac/win版

JetBrains Rider 2023激活版下载是一款专为.NET开发者打造的强大集成开发环境&#xff08;IDE&#xff09;。这款IDE提供了丰富的功能&#xff0c;旨在帮助开发者更快速、更高效地编写、调试和测试.NET应用程序。 Rider 2023 软件获取 Rider 2023在保持了其一贯的智能代码补全…

Git笔记——3

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、合并模式和分支策略 二、bug分支 三、强制删除分支 四、创建远程仓库 五、克隆远程仓库_HTTPS和_SSH 克隆远程仓库_HTTPS 克隆远程仓库_SSH 六、向远程仓库…

1.1_1 计算机网络的概念、功能、组成和分类

文章目录 1.1_1 计算机网络的概念、功能、组成和分类&#xff08;一&#xff09;计算机网络的概念&#xff08;二&#xff09;计算机网络的功能&#xff08;三&#xff09;计算机网络的组成1.组成部分2.工作方式3.功能组成 &#xff08;四&#xff09;计算机网络的分类 总结 1.…

Qt QWidget 简约美观的加载动画 第五季 - 小方块风格

给大家分享两个小方块风格的加载动画 &#x1f60a; 第五季来啦 &#x1f60a; 效果如下: 一个三个文件,可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QGridLayout> int main(int argc, char *arg…

sql-labs第46关 order by盲注

sql-labs第46关 order by盲注 来到了第46关进入关卡发现让我们输入的参数为sort&#xff0c;我们输入?sort1尝试&#xff1a; 输入?sort2,3,发现表格按照顺序进行排列输出&#xff0c;明显是使用了order by相关的函数。 我们将参数变成1进行尝试&#xff0c;就会报错&…

Rust之构建命令行程序(四):用TDD(测试-驱动-开发)模式来开发库的功能

开发环境 Windows 11Rust 1.75.0 VS Code 1.86.2 项目工程 这次创建了新的工程minigrep. 用测试-驱动模式来开发库的功能 既然我们已经将逻辑提取到src/lib.rs中&#xff0c;并将参数收集和错误处理留在src/main.rs中&#xff0c;那么为代码的核心功能编写测试就容易多了。我…

抽象的后端

Connection refused: no further information 出现这条代码的核心是你使用redis&#xff0c;但是本地没有开启redis服务 如何启动redis服务 第一步&#xff1a;确定你安装了对应的框架 以spring为例 <dependency><groupId>org.springframework.boot</group…

初识Lombok

前言 最近读一些公司的业务代码&#xff0c;发现近几年的java项目工程中都使用了lombok&#xff0c;lombok是一个可以自动生成get,set、toString等模板类方法的工具框架&#xff0c;程序再引入lombok后&#xff0c;添加一个注解便可以不写get\set\toString等方法。 Lombok示例…

【DL】深度学习之语音识别

目录 1 核心概念 2 安装依赖库 3 实践 语音信号处理&#xff08;Speech Signal Processing&#xff09;简称语音处理。 语音识别&#xff08;ASR&#xff09;和自然语言处理&#xff08;NLP&#xff09;&#xff1a;语音识别就是将语音信号转化成文字文本&#xff0c;简单实…

UE5 C++ Widget练习 Button 和 ProgressBar创建血条

一. 1.C创建一个继承Widget类的子类&#xff0c; 命名为MyUserWidget 2.加上Button 和 UserWidget的头文件 #include "CoreMinimal.h" #include "Components/Button.h" #include "Blueprint/UserWidget.h" #include "MyUserWidget.genera…

微信群发一次能发1000个好友了!

微信为我们日常交流不可或缺的沟通工具 群发功能在日常生活中使用得也比较多 但有时候 比如在节假日&#xff0c;需要向所有客户发送祝福时 在搞活动时&#xff0c;想向所有客户发起邀约时 如果一个一个点击来发送信息 会非常麻烦 但是&#xff01;&#xff01; 我今天…

提升Vue3应用效率的秘诀:深入比较ref与reactive!

ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型&#xff0c;而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象&#xff0c;但 Vue3 官方文档更推荐使用 ref。 我的想法&#xff0c;ref就是比reactive好用&#xff0c;官方也…

安防视频监控平台EasyNVR级联视频上云管理平台EasyNVS,出现报错“i/o deadline reached”该如何解决?

上云网关管理平台EasyNVS视频综合管理系统具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;系统可以将接入的视频资源实现视频能力统一输出&#xff0c;并能进行远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户反…

下载 axios.js 文件到本地【linux】

方式一 npm install axios在$NODE_PATH/node_modules/axios/dist路径下即可找到axios.js。 方式二 1、百度搜索 GitHub 官网&#xff1a;https://github.com/ 2、搜索 axios 3、点击 axios/axios 4、下载到本地 5、解压&#xff0c;进入到 dist 文件夹** 参考&#x…