椋鸟C语言笔记#28:匿名结构体、结构体的内存对齐、结构体传参、位段

萌新的学习笔记,写错了恳请斧正。


目录

在定义结构体时起别名

匿名结构体

结构体的自引用

结构体的内存对齐

offsetof

内存对齐练习

为什么要内存对齐

平台原因

性能原因

书写规范

修改默认对齐数

结构体传参

位段(位域)

位段的概念

位段的声明

位段的内存分配

位段的特殊声明

位段的跨平台性

位段注意事项


关于结构体的基本内容(包括结构体的声明、创建、初始化、结构成员访问)已经在笔记#15中讲述,不再赘述。

在定义结构体时起别名

定义结构体时前面直接加typedef进行起别名的操作不会影响结构体的创建。

typedef struct a
{int a;float b;char c;
} sta;

这就是定义了一个结构体类型struct a,然后给它起别名为类型sta

但是这样就不能在定义结构体类型的时候直接创建结构体变量了

注意(相关内容看下面):

  • 匿名结构体可以起别名,这样就能正常使用了
  • 自引用同时起别名不能用别名自引用(创建的优先顺序高于起别名)

匿名结构体

结构体在声明时,其实可以省略结构体标签(名称)。如下:

struct
{int a;float b;char c;
} a;

这就是匿名的创建了一个结构体并声明了一个该类型的结构体变量。

注意,在不起别名的情况下:

  • 匿名创建结构体如果没有直接声明几个对应的结构体变量,之后就再也不能申请了。
  • 匿名创建结构体之后也再也无法找到这个结构体类型了。
  • 两个成员相同的匿名结构体不会被认为是同一种结构体,比方说:
struct
{int a;float b;char c;
} a;struct
{int a;float b;char c;
} b, *p;

在上面这种情况下,如果令p等于&a就是非法的,因为两个匿名结构体类型不一样。

但是我们可以在创建匿名结构体的时候起别名,这样就能通过别名正常使用结构体了:

typedef struct
{int a;float b;char c;
} st;

比方说上面这段代码就能继续通过st这个类型名继续进行创建变量等操作

结构体的自引用

结构体可以自引用,常用于链表(以后讲)

当然这不是说结构体的成员可以是该结构体本身

(如果这样就无限套娃了,大小无穷大)

结构体的自引用指的是结构体的成员变量可以是该结构体的指针类型

比方说:

struct chain
{int data;struct chain* next;
};

如果起别名和自引用同时进行的话,自引用的地方不能用别名

比方说这样是不行的:

typedef struct chain
{int data;st* next;
} st;

应该写成这样(这边顺便把指针起别名了):

typedef struct chain
{int data;struct chain* next;
} st;typedef st* pst;

结构体的内存对齐

我们现在研究一下结构体的内存大小

结构体类型占内存的大小是不是等于所有成员变量占内存的和呢?

我们写一段程序验证一下:

#include <stdio.h>struct S1
{char c1;int i;char c2;
};int main()
{printf("%d\n", sizeof(struct S1));return 0;
}

这段代码在Win11 VS2022 x64 Debug的环境下输出12

而我们知道如果单纯的成员变量大小相加,答案应该为6

所以,结构体内存究竟是如何排布的呢?

其实结构体在内存中的排布,遵循内存对齐规则

1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
        对齐数 = 编译器默认的对齐数该成员变量大小 的 较小值

        VS2022 的默认对齐数为8,Linux gcc没有默认对齐数

3. 结构体总大小为结构体成员中最大的对齐数的整数倍

4. 如果结构体嵌套了结构体,嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍处,并且占据单独计算自身大小那么大的空间(因为内存对齐浪费的空间不会被下一个成员利用)。结构体总大小就是所有对齐数(包括嵌套结构体中的成员)中最大对齐数的整数倍

offsetof
//定义于头文件 <stddef.h> 
#define offsetof(type, member) /*implementation-defined*/ 

在stddef.h头文件中有一个宏offsetof,可以返回一个成员在结构体中的偏移量

其第一个参数是结构体类型名,第二个参数是成员变量名

其返回值可以用%zd、%zu接收

内存对齐练习
#include <stdio.h>struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};struct S3
{double d;char c;char i;
};struct S4
{char c1;struct S3 s3;char d;
};int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));printf("%d\n", sizeof(struct S3));printf("%d\n", sizeof(struct S4));return 0;
}

在Win11 VS2022 x64 Debug的环境下,4段输出为12、8、16、32

为什么要内存对齐

其实这是一种空间换时间的做法

平台原因

不是所有的硬件平台都能访问任意地址上的任意数据,某些平台只能在某些地址处(对齐的位置)取对应类型的数据,否则硬件异常。

性能原因

内存对齐的情况下访问速度一般会更快

访问未对齐内存的数据,处理器可能需要作两次内存访问(内存是一段一段访问的,数据不对齐可能存放在两个内存的访问段内),而对齐的内存访问仅需要一次访问

书写规范

所以为了节省空间,我们创建结构体应该尽量使较小的成员变量在前面,较大的放在后面

修改默认对齐数

我们可以使用预处理指令#pragma来修改编译器默认的对齐数

#include <stdio.h>#pragma pack(1)    //设置默认对⻬数为1struct S
{char c1;int i;char c2;
};#pragma pack()    //取消设置的对⻬数,还原为默认int main()
{printf("%d\n", sizeof(struct S));return 0;
}

上面这段代码的输出结果就为6

结构体传参

与其他数据类型类似,结构体传参也分为直接传参与传地址两种

#include <stdio.h>struct S
{int data[3];int num;
} s = { {1,2,3}, 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;
}

两种方式作用相同,但是我们优先使用传地址的方式

因为函数传参时需要拷贝实参作为形参压栈,如果传递结构体本身会占用较多的内存

位段(位域)

位段的概念

位段是一种特殊的结构体类型,其成员的内存宽度可以被我们规定

位段成员必须是int、signed、unsigned之间的一种(C99以前)

C99标准开始,位段成员也可以使用布尔类型

位段的声明

位段的声明与结构体类似,但是成员名(可以省略代表直接浪费一段空间)后有一个冒号和数字:

#include <stdio.h>struct A
{int _a : 2;signed _b : 5;unsigned _c : 10;int _d : 30;
};int main()
{printf("%d\n", sizeof(struct A));return 0;
}

这段程序在Win11 VS2022 x64 Debug的环境下的输出结果为8

为什么呢?这与位段的内存分配有关

位段的内存分配

位段声明中冒号后面的数字就代表了其被规定占据多少个比特位

而整个位段总大小是按4个字节(int类)或者1个字节(_Bool)逐步分配

上述代码中位段A,内存申请了一次4个字节(32位)。这32位填充了_a、_b、_c后只剩下15位了,发现不够继续填充_d,就再次申请了4个字节用来填充数据_d,所以总共占据了8个字节。

位段的特殊声明

相邻的几段如果类型占据的空间大小一致可以打包起来写在一起(通常可以),比方说:

struct B
{int _a : 2, _b : 5,  _c : 10;int _d : 30;
};    //宽8

规定空间可以省略,代表占据一整个类型的空间,比方说:

struct B
{int _a : 2, _b : 5,  _c : 10;int _d;
};    //宽8

这里_d就占据了整个4字节(32位)的空间

成员名可以省略用来占据一定不被使用的空间:

struct C
{unsigned _a : 2;signed _b : 5;int _c : 30, _d : 1, _e : 3;int _f : 3, :2, _g : 4;
};    //宽12

这里_f和_g直接有两个比特是被占位的

如果宽度规定为0(即零位域,必须未命名)代表直接开始下一个分配单元,这边剩下来的丢掉:

struct D
{unsigned _a : 2, :0;signed _b : 5, :0;int _c : 30, _d : 1, _e : 3;int _f : 3, : 2, _g : 4;
};    //宽16
位段的跨平台性

位段跨平台性很差,原因如下:

  • int位段被当做有符号还是无符号是不确定的
  • 机器的位数不一样导致类型宽度不一样
  • 位段在每个分配单元中数据从左往右填还是从右往左填不确定
  • 还有很多其他原因

所以,虽然位段很省空间,没事还是不要用位段

位段注意事项

位段不能取地址,不能有指针变量,会报错

因为位段的成员的起始位置可以不在整字节处,没有地址


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

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

相关文章

Python文件命名规则:批量重命名与规则匹配的文件

我从一个旧的 iOS 项目中获得了一个文件夹&#xff0c;其中包含许多类似于 image.png image2x.png another-image.png another-image2x.png然而&#xff0c;由于该项目现在只需要 2x.png 图像&#xff0c;我已经删除了所有的文件没有 2x 的名称。 但是我现在想知道如何轻松…

【UML】第5篇 UML中的视图和图

目录 一、视图和图 二、图的种类 2.1 结构图 2.2 行为图 图是UML中最重要的概念了&#xff0c;起码我是这么认为。 上篇关于低代码的文章&#xff0c;我也说了&#xff0c;未来也许AI编码&#xff0c;我们更重要的工作&#xff0c;是能够为业务进行建模&#xff0c;拆解&a…

mybatis plus 公共字段自动填充createBy updateBy

一、公共字段自动填充 需求&#xff1a;好多表公共的字段&#xff0c;赋值逻辑也相同&#xff0c;不用每次为其赋值&#xff0c;‘拦截器’统一赋值。 1. 在新增数据时&#xff0c;需要设置创建时间、创建人、修改时间、修改人等字段&#xff0c;在编辑数据时需要设置修改时间…

FL Studio21.2.2963水果音乐软件安装

FL Studio是功能强大的音乐制作解决方案&#xff0c;使用旨在为用户提供一个友好完整的音乐创建环境&#xff0c;让您能够轻松创建、管理、编辑、混合具有专业品质的音乐&#xff0c;一切的一切都集中在一个软件中&#xff0c;只要您想&#xff0c;只要您需要&#xff0c;它总能…

深兰科技入选财联社“2023科创好公司”榜单

12月13日&#xff0c;“2023科创好公司”评选榜单正式公布&#xff0c;深兰科技成功入选&#xff0c;获得该榜单中“新能源汽车及自动驾驶”赛道的“科创好公司”称号。 “科创好公司”榜评选是由财联社及《科创板日报》联合打造的一级市场投后服务体系中的重要活动项目&#x…

Axure 9基本元件,表单及表格元件简介,表单案例

目录 一.基本元件 1.元件基本介绍 2.基本元件的使用 二.表单及表格元件 三.表单案例 四.简单简历绘制 一.基本元件 1.元件基本介绍 概述 - 在Axure RP中&#xff0c;元件是**构建原型图的基础模块**。 将元件从元件库里拖拽到画布中&#xff0c;即可添加元件到你的原型…

什么店生意好?C++采集美团商家信息做数据分析

最近遇到几个朋友&#xff0c;想要一起合伙投资一个实体店&#xff0c;不问类型&#xff0c;就看哪类产品相对比较受欢迎。抛除地址位置&#xff0c;租金的影响&#xff0c;我们之谈产品。因此&#xff0c;我熬了几个通宵&#xff0c;写了这么一段爬取美团商家商品信息的数据并…

如何在 Windows 10/11 上恢复永久删除的文件夹

如果您曾经错误地删除过某个文件夹&#xff0c;您就会知道随之而来的恐慌。您认为当您在某些内容上单击“删除”时&#xff0c;它就会永远消失。但情况并非总是如此。您可以使用几种不同的方法来恢复已删除的文件夹 。 本指南将向您展示如何在 Windows 10/11 上恢复永久删除的…

Axure RP - 交互设计的强大引擎

目录 前言 1. 交互设计&#xff1a;连接用户与产品的纽带 2. 情景设计&#xff1a;预测用户行为的未来 3. 演示和共享&#xff1a;让设计活起来 我的其他博客 前言 在数字化时代&#xff0c;用户体验的重要性日益突显&#xff0c;而交互设计成为塑造产品与用户互动的关键。…

colmap三维重建核心逻辑梳理

colmap三维重建核心逻辑梳理 1. 算法流程束流2. 初始化3. 重建主流程 1. 算法流程束流 重建核心逻辑见 incremental_mapper.cc 中 IncrementMapperController 中 Reconstruct 初始化变量和对象判断是否有初始重建模型&#xff0c;若有&#xff0c;则获取初始重建模型数量&am…

基于Python实现的一个书法字体风格识别器源码,通过输入图片,识别出图片中的书法字体风格,采用Tkinter实现GUI界面

项目描述 本项目是一个书法字体风格识别器&#xff0c;通过输入图片&#xff0c;识别出图片中的书法字体风格。项目包含以下文件&#xff1a; 0_setting.yaml&#xff1a;配置文件&#xff0c;包含书法字体风格列表、图片调整大小的目标尺寸等设置。1_Xy.py&#xff1a;预处理…

大模型公司「卷」 芯片 英伟达危?

人工智能的行业竞争&#xff0c;不仅是OpenAI与谷歌等各大互联网巨头的大模型之争&#xff0c;支撑运算的芯片赛道也暗流涌动&#xff0c;大模型“制造机”们也加入了赛道。 OpenAI计划从CEO Sam Altman投资的一家初创公司Rain AI订购更高效的NPU芯片&#xff1b;微软也推出两…

SourceTree 免登录跳过初始设置

用于Windows和Mac的免费Git客户端。 Sourcetree简化了如何与Git存储库进行交互&#xff0c;这样您就可以集中精力编写代码。通过Sourcetree的简单Git GUI可视化和管理存储库。 SourceTree 安装之后需要使用账号登陆以授权&#xff0c;以前是可以不登陆的&#xff0c;但是现在是…

neuq-acm预备队训练week 10 P1129 [ZJOI2007] 矩阵游戏

题目描述 小 Q 是一个非常聪明的孩子&#xff0c;除了国际象棋&#xff0c;他还很喜欢玩一个电脑益智游戏――矩阵游戏。矩阵游戏在一个 nn 黑白方阵进行&#xff08;如同国际象棋一般&#xff0c;只是颜色是随意的&#xff09;。每次可以对该矩阵进行两种操作&#xff1a; 行…

为什么在Android中需要Context?

介绍 在Android开发中&#xff0c;Context是一个非常重要的概念&#xff0c;但是很多开发者可能并不清楚它的真正含义以及为什么需要使用它。本文将详细介绍Context的概念&#xff0c;并解释为什么在Android应用中需要使用它。 Context的来源 Context的概念来源于Android框架…

Win32程序与MFC程序构建顺序梳理

Windows程序的生成顺序 Windows窗口的生命周期 初始化操作 从WinMain函数开始&#xff0c;注册窗口&#xff1b;创建窗口&#xff1b; 调用CreateWindow,为程序建立了一个窗口&#xff0c;作为程序的屏幕 舞台。CreateWindow产生窗口之后会送出WM_CREATE消息给窗口函数&…

新手运行若依项目|若依项目各部分介绍|并修改自己需要的页面

新手运行若依项目|若依项目各部分介绍|并修改自己需要的页面 文章目录 新手运行若依项目|若依项目各部分介绍|并修改自己需要的页面前言IEDA如何运行若依项目若依项目目录简介ruo-yi common工具类ruoyi-framework 框架核心其他部分ruo一admin后台服务通用配置 application.yml数…

关于“Python”的核心知识点整理大全26

目录 10.3.9 决定报告哪些错误 10.4 存储数据 10.4.1 使用 json.dump()和 json.load() number_writer.py number_reader.py 10.4.2 保存和读取用户生成的数据 对于用户生成的数据&#xff0c;使用json保存它们大有裨益&#xff0c;因为如果不以某种方式进行存储&#xf…

redux实现原理?

Redux 是一个 JavaScript 状态管理库&#xff0c;它可以用于管理应用程序中的状态。Redux 的实现原理可以简单概括为以下几个步骤&#xff1a; 创建 store&#xff1a;创建一个全局的存储对象作为状态管理的中心。Store 由 Redux 提供&#xff0c;它包含应用程序的当前状态&am…

小红书kop营销策略有哪些,达人投放总结!

从kol到koc&#xff0c;当今时代产品种草模式&#xff0c;层出不穷。品牌想要跟上市场更新迭代的洪流&#xff0c;就需要时刻了解新型的营销方式。那么对于新型的kop模式你了解多少呢?我们今天就将详细分享小红书kop营销策略有哪些&#xff0c;达人投放总结&#xff01; 一、什…