结构体的内存对齐与位段的实现

news2025/1/13 19:40:01

本篇文章重点介绍结构体相关知识以及深入介绍的结构体的内存对齐与位段的实现 ———————————— 内存对齐+位段——————————————————

  • 一.结构体
    • 1.结构体类型的声明
      • 1.1基础知识
      • 1.2声明
      • 1.3特殊声明
      • 1.4结构体的自引用
      • 1.5结构体变量的定义和初始化与访问
    • ==2.结构体内存对齐==
      • 2.1如何计算?
      • 2.3为什么存在内存对齐?
  • 二.位段
    • 2.1什么是位段
    • 2.2位段的内存分配
    • 2.3位段的跨平台问题
    • 2.4位段的应用

一.结构体

1.结构体类型的声明

1.1基础知识

结构体是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。

1.2声明

struct tag//标签,可以随便写,但最好有意义
{
	结构体成员;
	//member - list;
}结构体变量;

比如描述一个学生:

struct Stu
{
	char name[10];//姓名
	int age;//年龄
	char sex[10];//性别
	char id[10];//学号
}s;//创建一个结构体变量s

1.3特殊声明

在结构体声明的时候,有时可以不完全声明,也叫做匿名声明
比如:

struct //不写标签,这种属于匿名结构体类型
{
	int a;
	int b;
	char c;
}x;
struct
{
	char arr[10];
	double f;
}*p;
  1. 注意上面这两种结构体都是属于匿名结构体类型,不告诉你名字,这种结构体类型如果要使用必须在声明的时候就在后面定义变量,不能再到主函数里面引用,因为你不知道这个结构体的名字是什么,所以必须在声明的时候就定义变量。
    2.如果在上面的代码的基础上这样写有问题吗?
    p=&x;
    这两个都是结构体类型能这样写吗?

答案是不能:
警告:
编译器会把上面的两个声明当成完全不同的两个类型,所以是不可以的。

1.4结构体的自引用

  1. 什么叫结构体的自引用呢?
    2.在结构体中包含一个类型为该结构本身的成员是否可以呢?
//代码1
struct Node
{
	int data;
	struct Node next;
};
//这样可以吗?
如果可以那想想sizeofstruct Node)的大小是多少呢?

正确的自引用方式:

struct Node
{
	int data;
	struct Node* next;
};
//应该里面放的是指向该类型的指针,而不是直接将该类型的变量直接放进去

还有一个注意点:
typedef重命名可以让结构体声明简单一些:

typedef struct Node
{
	int data;
	struct Node*next
}Node;//将结构体 struct Node类型重命名为Node

1.5结构体变量的定义和初始化与访问

结构体类型知道如何声明后,那怎么定义变量呢?

struct Point
{
	int x;
	int y;
}p1;//结构体声明时定义变量
struct Point p2;//或者声明完后,再进行定义变量
struct Point p3 = { 10,20 };//定义变量的时候,初始化变量

struct Stu//类型声明
{
	char name[10];
	int age;
	char sex[20];
}s1;//声明的时候定义变量
s1 = { "zhangsan",18,"nan" };//初始化
struct Stu s2 = { "xiao tao",20,"nan" };//定义变量的时候初始化

struct Node
{
	int data;
	struct Point p1;
}n1 = { 1,{2,3} };//结构体嵌套初始化
struct Node n2 = { 10,{15,20} };//结构体嵌套初始化

2.结构体内存对齐

我们已经基本了解结构体的基本知识了
现在我们开始深入探讨一个问题:结构体大小怎么计算
这涉及一个关键知识点:内存对齐
我们先来看一个问题:

struct S1
{
	char c1;//1个字节
	int i;//4字节
	char c2;//1字节
};
struct S2
{
	char c1;//1字节
	char c2;//1字节
	int i;//4字节
};
int main()
{   //结构体S1 和结构体S2成员都一样只不过位置不同,那大小一样吗?按理讲结构体成员大小相加就是结构体大小了吧,但真的是这样吗?
	printf("%d\n", sizeof(struct S2));
	printf("%d\n", sizeof(struct S1));
	return 0;
}

结果:
在这里插入图片描述
这个结果告诉我们结构体成员在内存中并不是简单的按照大小进行连续的排列的而是按照某种规则进行排列的。那这个规则是怎样的呢?

2.1如何计算?

首先要掌握结构体内存对齐规则

  1. 第一个成员在偏移为0的地址处(结构体变量偏移量)
  2. 从第二个成员开始,每个成员都要对齐到一个对齐数的整数倍数处
    对齐数:结构体成员自身大小和默认对齐数中较小值
    VS中默认对齐数是8
    Linux gcc中没有对齐数,对齐数默认是结构体成员大小
  3. 结构体最终的大小必须是结构体成员中对齐数最大的整数倍
  4. 如果结构体中嵌套了结构体成员,嵌套的结构体对齐数就是该结构体中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体中的对齐数)的整数倍。

我们来做几个题目来熟悉熟悉吧
就拿上面的问题来解答吧:
1.
在这里插入图片描述
2.
在这里插入图片描述

//练习3
struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3))

//练习4-结构体嵌套问题
struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
printf("%d\n", sizeof(struct S4))

练习3图解:
在这里插入图片描述
练习4图解:
在这里插入图片描述
在这里插入图片描述

2.3为什么存在内存对齐?

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

2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问就只需要一次访问。
在这里插入图片描述

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,该如何做到呢?
答:让占用空间小的成员尽量集中到一起。

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

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

二.位段

结构体实现位段的能力:
位段和结构体相似,只是对结构体进行了相应的限制,需要多少内存就申请多少内存。

2.1什么是位段

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

  1. 位段的成员必须是 int,unsigned int 或者signed int 或者整形家族的char类型
  2. 位段成员后面要有一个冒号和一个数字
    例如:
struct A
{
	int _a : 3;
	int _b : 4;
	int _c : 15;
	int _d : 20;
};

A就是一个位段类型
那位段A的大小是多少呢?

int main()
{
printf(“%d”, sizeof(struct A));
return 0;
}

2.2位段的内存分配

  1. 位段的成员可以是 int unsigned int ,signed int或者是char类型的(整形家族里的)
  2. 位段的空间每次开辟是按照需要以4个字节(int类型)或者1个字节(char类型)的方式来开辟的
  3. 位段涉及很多不确定的因素,位段是步跨平台的,如果注重可移植性应该避免使用位段。

举一个例子:

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

在这里插入图片描述
1.位段都是限制好每个变量所需要的空间大小。
2.在VS2022环境上每个字节(8个比特位)分配内存是从低位到高位的
3.(char类型)一个字节所提供的空间大小不足变量所需的,要舍弃剩余空间,重新开辟一个字节。
在这里插入图片描述

2.3位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大位的数目是不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:
跟结构体相比,位段只要计算好也可以达到相同的效果,但可以很好的节省空间,但存在跨平台问题。

2.4位段的应用

在这里插入图片描述

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

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

相关文章

C 语言零基础入门教程(八)

C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。 C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false…

JVM快速入门学习笔记(四)

15.GC :垃圾回收机制 垃圾回收的区域只有在堆里面(方法区在堆里面) 15.1 垃圾回收 GC JVM 在进行垃圾回收(GC)时,并不是堆这三个区域统一回收。大部分时候,回收都是新生代~   1.新生代   …

Opencv项目实战:19 手势控制鼠标

目录 0、项目介绍 1、效果展示 2、项目搭建 3、项目代码展示 HandTrackingModule.py VirtualMouse.py 4、项目资源 5、项目总结 0、项目介绍 在Opencv项目实战:15 手势缩放图片中,我们搭建了HandTrackingModule模块,但在这里你还得用…

离散数学与组合数学-04图论上

文章目录离散数学与组合数学-04图论上4.1 图的引入4.1.1 图的示例4.1.2 无序对和无序积4.1.3 图的定义4.2 图的表示4.2.1 集合表示和图形表示4.2.2 矩阵表示法4.2.3 邻接点与邻接边4.3 图的分类4.3.1 按边的方向分类4.3.2 按平行边分类4.3.3 按权值分类4.3.4 综合分类方法4.4 图…

MySQL —— 表操作

目录 一、创建表 二、创建表的案例 三、查看表的结构 四、修改表 五、删除表 一、创建表 语法: CREATE TABLE [IF NOT EXISTS] table_name(field1 datatype1 [COMMENT 注释信息],field2 datatype2 [COMMENT 注释信息],field3 datatype3 [COMMENT 注释信息] )…

通信原理简明教程 | 模拟信号的数字化传输

文章目录1 抽样及抽样定理1.1 抽样1.2 抽样定理2 量化及量化信噪比2.1 均匀量化2.2 量化误差和量化信噪比2.3 非均匀量化3 编 码3.1常用的二进制码组3.2 均匀量化编码方法3.3 A律13折线编码4 脉冲编码调制系统4.1 PCM系统的码元速率4.2 PCM系统的抗噪声性能5 预测编码5.1 差分脉…

【Kotlin】扩展函数 ③ ( 定义扩展文件 | 重命名扩展函数 | Kotlin 标准库扩展函数 )

文章目录一、定义扩展文件二、重命名扩展函数三、Kotlin 标准库扩展函数一、定义扩展文件 如果定义的 扩展函数 需要在 多个 Kotlin 代码文件 中使用 , 则需要在 单独的 Kotlin 文件 中定义 , 该文件被称为 扩展文件 ; 定义 标准库函数 的 Standard.kt 就是 独立的 扩展文件 ;…

IDEA搭建Finchley.SR2版本的SpringCloud父子基础项目-------Feign负载均衡

1.概述 官网:http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支…

[Linux]进程地址空间

🥁作者: 华丞臧. 📕​​​​专栏:【LINUX】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文…

谁你的财神 谁是你的穷神

送穷神,迎灶神,下午提前准备迎接财神 我们说一个人穷,揭不开锅了,只能喝凉水了,到后来只能喝西北风 谁是我们的财神,信任我们的人,帮助我们的人,感谢过往贵人的资助 但是信任是不对…

【数据结构】算法复杂度

文章目录引入算法复杂度一.时间复杂度定义大O渐进表示法经典例题常量字符串二分查找冒泡排序递归1.阶乘2.斐波切纳数列二.空间复杂度定义经典例题冒泡排序递归1.阶乘2.斐波切纳数列引入 为什么要有算法复杂度? 当我们正在解决一个问题的时候,想出了多种思…

【计算机基础】操作系统

前言:本文适合用于基础了解和背诵,尽可能采用流畅且简短的语言回答操作系统相关问题并且辅以图片帮助记忆,不适合用作详细了解与深入学习 。 总述 操作系统部分主要分为以下几个重点 —— 操作系统基础 、 进程和线程、操作系统的内存管理、…

JAVA基础知识07常用API

目录 1. 常用API 1.1 简介 1.2 String类 1.2.1 字符串在开发中的应用场景 1.2.2 String 类的特点 1.2.3 String 类的常见构造方法 1.2.4 String 类的常见面试题 1.2.5 面试题题解 1.2.6 String 类用于比较的方法 1.2.7 String 字符串的遍历 1.2.8 String 字符串的截取…

QByteArray字节流和二进制与字符的关系以及tohex() toLatin1

QByteArray 存储的是字节,二进制形式,即ascii码的二进制编码。输出的时候,会输出二进制对应的字符 即一个映射: 二进制到ascii码的映射而.tohex()会将二进制转化为16进制字符,这里的16进制字符又是作为值域了,实际存…

JavaEE-文件和IO(二)

目录2.2 文件内容相关的操作三、文件操作案例3.1 案例一3.2 案例二3.3 案例三2.2 文件内容相关的操作 打开文件读文件写文件关闭文件 针对文件内容的读写,java标准库提供了一组类~ 首先按照文件的内容,分为两个系列 字节流对象,针对二进制…

手把手教你写web全栈入门项目—React+Koa+MongoDB(3w字教程,真的很详细,有代码)

手把手教你写web全栈入门项目—ReactKoaMongoDB 文章目录手把手教你写web全栈入门项目—ReactKoaMongoDB前言一、推荐基础二、所需环境三、软件四、项目源码五、文章结构六、遇到问题怎么办前端一、页面登录页首页二、目录结构三、技术选择四、开始项目1、页面组件1.1 目录1.2 …

JavaScript 所见所得文本编辑器 Froala Editor 4.0.17Crack

Froala Editor v4.0.17 清除格式工具现在可以从粘贴的内容中删除内联样式。 2023 年 1 月 24 日 - 9:07新版本 特征 清除格式工具现在可以从粘贴的内容中删除内联样式。 改进的“删除时保留格式”功能可保留已删除文本的格式并将其应用于替换文本。 选择图像时,用于…

day20|77. 组合。回溯的开始

回溯思路 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果} } 77. 组合…

91.使用注意力机制的seq2seq以及代码实现

1. 动机 2. 加入注意力 key和value是一样的 假设英语句子长为3的话,就会有3个key-value pair,key和vlaue是一个东西,每一个key-value pair对应第i个词的RNN的输出。之前的seq2seq只使用了最后的key-value,现在则是把所有的key-val…

JavaWeb语法八:网络原理初识

目录 1.局域网与广域网 1.1:局域网 1.2:广域网 2:网络基础知识 3.协议分层 3.1:分层的好处 3.2:TCP/IP五层(或四层)模式 4:封装和分用 4.1:封装 4.2&#xff1…