结构体内存对齐与结构体位段:学习笔记8

news2025/1/18 11:00:55

目录

一.结构体基础知识

1. 结构体的特殊声明 

2. 结构的自引用

 3.结构体变量的定义和初始化

二.结构体内存对齐

1.关键概念: 

2.计算示例 

3.嵌套结构体的内存计算

4.结构体内存对齐的意义

5.定义结构体时的注意事项

6.修改默认对齐数

附:关于结构体传参:

 三.结构体实现位段

1.位段的介绍

2.位段成员的内存分布: 

3.位段的跨平台问题 


一.结构体基础知识

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

结构体,联合体,枚举以及数组都是自定义类型;

struct tag
{
    member-list;    成员定义;
}variable-list;     变量定义;(定义类型的同时创建变量(全局变量))

1. 结构体的特殊声明 

匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;             
struct
{
    int a;
    char b;
    float c;
}a[20], *p;

上述结构体声明方式中省略了结构体的标识名,这种方式定义的结构体类型无法在后续的变量创建中使用,此种类型结构体的变量实例只能在该类型声明的同时创建。

同时注意:

在上面代码的基础上语句p = &x;是不合法的,编译器会认为代码段中两个结构体类型是不同的。

2. 结构的自引用

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

在结构体内部定义指向本结构体类型的指针变量叫做结构的自引用。

用typedef定义的结构体类型:

typedef struct Node
{
    int data;
    struct Node* next;
}Node;       这里的Node不是创建的变量,而是重定义的类型名;

此时的struct Node类型名被typedef关键字声明为Node。

后续引用该类型时就只需用Node作为类型名即可,书写上方便了许多。

 3.结构体变量的定义和初始化

struct Point
{
    int x;
    int y;
}p1;             //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2

struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL};               结构体嵌套初始化(定义类型的同时创建变量)

struct Node n2 = {20, {5, 6}, NULL};   结构体嵌套初始化(引用类型创建变量)

二.结构体内存对齐

一个结构体所占的内存大小的计算并不是简单地将其成员变量的大小直接相加,结构体内部成员在内存上的分布遵循内存对齐规则。

1.关键概念: 

(1)结构体内部地址的偏移量:结构体内部某个地址的偏移量指的是结构体内存空间中某个字节的地址结构体首地址的差值(以字节为单位)(结构体的内存中存储结构是从低地址向高地址排列的,与数组相同)。

(2)对齐数:结构体的每个成员都有自己的对齐数。

结构成员对齐数 =  编译器默认的一个对齐数 与 该成员大小(单位为字节)的较小值。

注意确认成员对齐数时,数组成员要视为多个内置类型成员来看待,不能将数组成员看做一个整体。比如char a[3]作为某个类型的结构体的成员,那么确认成员对齐数时要将其看成3个char类型的变量而不能将其看成一个整体。
 

(VS中默认的对齐数的值为8)

(注意Linux gcc环境下没有默认对齐数,结构成员对齐数就是成员自身所占的字节数)

结构体成员的内存对齐规则:

1.结构体第一个成员的地址的偏移量为0。(即第一个成员存储在结构体首地址处)

2.其他成员变量的地址的偏移量必须为该成员的对齐数的整数倍。
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍 

基于以上规则,就可以计算一个结构体所占内存大小。

2.计算示例 

结构体所占内存大小的计算示例:

计算该类型结构体所占内存大小
struct A
{
	int a;
	short b;
	int c;
	char d;
};

3.嵌套结构体的内存计算

如果有嵌套结构体的情况,那么作为另外一个结构体的成员的结构体,其对齐数为自身内部成员的对齐数中的最大对齐数

结构体的整体大小就是所有成员的对齐数中的最大对齐数(含嵌套结构体的对齐数)的整数倍。

计算结构体S4所占内存的大小
struct S3
{
    double d;
    char c;
    int i;
};

struct S4
{
    char c1;
    struct S3 s3;
    double d;
};

 

4.结构体内存对齐的意义

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

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

比如如下情形:

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。是C语言的一种用于提高程序性能的内存管理机制。

5.定义结构体时的注意事项

基于结构体内存对齐规则:

定义结构体时,为了尽可能地节省空间,定义成员变量时应注意定义顺序:将相同类型的变量书写在一起。

比如 :

struct S1
{
    char c1;
    int i;
    char c2;
};
struct S2
{
    char c1;
    char c2;
    int i;
};

S1和S2类型的成员一模一样,但是S1类型结构体所占内存空间大小为12个字节,S2类型结构体所占内存空间为8个字节。显然S2的成员变量书写顺序更节省空间。

6.修改默认对齐数

编译器的默认对齐数是可以修改的:

利用指令:#pragma pack(填入对齐数的值)

就可以完成对齐数的修改。

比如:

#include <stdio.h>
#pragma pack(8)            设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()             取消设置的默认对齐数,还原为默认值
                           (此时struct S1类型已经按照默认对齐数为8定义好了)

#pragma pack(1)            设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()             取消设置的默认对齐数,还原为默认
                           (此时struct S1类型已经按照默认对齐数为8定义好了)

修改对齐数后 S1和S2的大小会有所差异
int main()
{
	
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

输出结果:

默认对齐数一般设置为2的N次方,不然内存对齐就没什么意义了(计算机读取数据的地址值的偏移量一般为2的整数倍数)。 

附:关于结构体传参:

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

 三.结构体实现位段

1.位段的介绍

1.位段的成员必须是 int、unsigned int 或signed int (或者其他整形家族的数据)。
2.位段的成员名后边有一个冒号和一个数字。

比如:

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

位段成员后面的冒号后的数字表示该位段成员在内存中所占的二进制位数(bits)。

其存在意义是可以根据实际需要定义成员变量的bit位数,可以节省系统的空间开销。

2.位段成员的内存分布: 

为位段成员开辟内存空间时,是根据成员的类型一段一段空间去开辟的,比如上面的struct A,  _a成员为int类型,该位段开辟内存时会先向系统申请4个字节(32bit)的空间,将_a,_b,_c都存入这四个字节的空间中,由于这4个字节无法再存下_d,所以需要再申请4个字节来存放30bit的成员 _d.

位段成员内存分布示例:

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的成员赋值后,其内存分布:

 

 

也就是说在单个字节内部,位段成员的二进制序列是按照低位向高位的顺序存储的(单字节内,位段成员在内存中的二进制序列和位段成员数值的二进制序列看起来是一致的)。

注意对于多字节的位段成员,其字节序遵循系统大小端排列原则。 

(图中为windows vs2022环境,字节序为小端存储) 

3.位段的跨平台问题 

不同的操作系统中,位段的实现会有差异:

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

所以跨平台程序一般不使用位段。

 

 

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

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

相关文章

【PWA学习】5. 使用 Notification API 来进行消息提醒

引言 在上一节, 介绍了如何使用 Push API 进行服务端消息推送。提到 Push 就不得不说与其联系紧密的另一个 API——Notification API。它让我们可以在“网站外”显示消息提示&#xff1a; 消息推送示例即使当你切换到其他 Tab&#xff0c;也可以通过提醒交互来快速让用户回到你…

webviz安装,docker安装可正常使用与Foxglove Studio

Foxglove Studio Foxglove Studio与webviz使用起来非常类似 去可以直接使用web也可以下载安装包 Foxglove Studio不提供源码 安装包下载地

linux cgroup、kubernetes limit

linux cgroup、kubernetes limit 1.cgroups 简介 cgroups&#xff0c;其名称源自控制组群&#xff08;control groups&#xff09;的缩写&#xff0c;是内核的一个特性&#xff0c;用于限制、记录和隔离一组进程的资源使用&#xff08;CPU、内存、磁盘 I/O、网络等&#xff0…

JSP——分页查询

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

Homekit智能家居DIY产品一智能面板开关

触摸开关&#xff0c;即通过触摸方式控制的墙壁开关&#xff0c;其感官场景如同我们的触屏手机&#xff0c;只需手指轻轻一点即可达到控制电器的目的&#xff0c;随着人们生活品质的提高&#xff0c;触摸开关将逐渐将换代传统机械按键开关。 触摸开关控制原理 触摸开关我们把…

【广度优先搜索遍历 BFS】单词接龙

一、题目描述 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk&#xff1a; - 每一对相邻的单词只差一个字母。 - 对于 1 < i < k 时&#xff0c;每个 si 都在 wordList 中。注意…

全面详解Java垃圾回收器

一&#xff1a;什么是垃圾回收 Java 方法栈、本地方法栈随着方法结束或者线程结束&#xff0c;堆中的对象是用完&#xff0c;都会进行回收内存&#xff0c;所以这些区域的内存分配和回收都具备确定性&#xff0c;不需要额外考虑回收的问题。而堆和方法区存储的对象可能只有在运…

Ad5761r GD32 STM32 驱动设计

MCU采用GD32,GD32基本上和STM32一样,针对ad5761r的时序操作是完全相同的.软、硬件设计已经再产品设计中实际使用。本文章提供参考硬件设计&#xff0c;以及对应的源代码&#xff0c;具体可以作为实际项目的参考设计AD5761R是一款单通道、16位串行输入、电压输出DAC。该器件采用…

网工进阶之路-锐捷NAT网络地址转换实验 ----尚文网络奎哥

实验拓扑&#xff1a;实验需求&#xff1a; 1&#xff1a;方框内设备为内网设备&#xff0c;方框外是外网设备&#xff0c;内网网段为192.168.1.0/24&#xff0c;外网路由器互联网段为100.1.1.0/24&#xff0c;外网PC网段为200.1.1.0/24 2&#xff1a;希望使用各种NAT实现内网…

2023年海外优青项目申报指南及政策解读

海外优青项目申报&#xff0c;一直备受海外优秀青年学者&#xff08;包括博士后研究人员&#xff09;关注。知识人网小编现将国家自然科学基金委员会公布的2023年申报指南全文摘录&#xff0c;并和往年加以对比进行政策解读&#xff0c;以飨读者。自2021年起&#xff0c;国家自…

【面试题】说说你对发布订阅、观察者模式的理解?区别?

大厂面试题分享 面试题库前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库一、观察者模式观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都将得到…

Elasticsearch-高级搜索(拼音|首字母|简繁|二级搜索)

需求&#xff1a; 中文搜索、英文搜索、中英混搜全拼搜索、首字母搜索、中文全拼、中文首字母混搜简繁搜索二级搜索&#xff08;对第一次搜索结果&#xff0c;再进行搜索&#xff09;一、ES相关插件 IK分词&#xff1a; GitHub - medcl/elasticsearch-analysis-ik: The IK A…

JsonWebToken远程代码执行漏洞(CVE-2022-23529)

漏洞描述 JsonWebToken 是一个用于创建、签名和验证 JSON Web Token开源库。node-jsonwebtoken是node.js 下 JsonWebToken 的实现。 在JsonWebToken < 8.5.1版本中由于jwt.verify()方法未对用户输入的secretOrPublicKey参数进行有效的检查。如果攻击者能够控制secretOrPub…

【docker12】docker复杂安装

docker复杂安装 之前是单机版&#xff0c;自娱自乐还是不错滴&#xff0c;但是如果是生产开发环境中是需要复杂集群安装的 1.安装mysql主从复制 1.1主从复制原理&#xff08;记得补&#xff09; 1.2主从搭建步骤 新建主服务器容器实例3307 命令&#xff1a; docker run -d …

Unity 实现一个特定动画状态切换树

前言 今天在工作中接到一需求,要求人物摆在不同的9个格子上,在哪个格子上,就走哪个格子动画播放逻辑; 打个比方:第一个格子上有台电脑,我将角色放上去,角色就玩电脑,每播完一次动画,就根据概率判断是否需要去喝水,最终实现的效果,就是将角色放上去,并且随机时间进行喝水;第二个…

TiDB 底层存储结构 LSM 树原理介绍

随着数据量的增大&#xff0c;传统关系型数据库越来越不能满足对于海量数据存储的需求。对于分布式关系型数据库&#xff0c;我们了解其底层存储结构是非常重要的。本文将介绍下分布式关系型数据库 TiDB 所采用的底层存储结构 LSM 树的原理。 1 LSM 树介绍 LSM 树&#xff08…

测试开发基础 mvn test | 利用 Maven Surefire Plugin 做测试用例基础执行管理

一、需求在测试工作场景中&#xff0c;经常会遇到下面的问题&#xff1a;1、执行自动化测试用例的时候&#xff0c;只想指定某个测试类&#xff0c;或者某个方法&#xff0c;又或者某一类用例等&#xff0c;怎么办&#xff1f;2、想要和 Jenkins 一起进行持续集成&#xff0c;可…

C语言_文件操作(下)

目录 8. 文件的随机读写 8.1 fseek 8.2 ftell 8.3 rewind ​9. 文件结束判定 10. perror 8. 文件的随机读写 假设文件中存放的是abcdef&#xff0c;如下图&#xff0c;通常在读文件时&#xff0c;是先读取首元素地址&#xff0c;也就是文件指针指向a&#xff0c;每读一…

【Linux进程信号】

Linux进程信号技术应用角度的信号信号的发送与记录信号处理常见方式产生信号通过终端按键产生信号通过系统函数向进程发信号由软件条件产生信号由硬件异常产生信号阻塞信号信号其他相关常见概念在内核中的表示sigset_t信号集操作函数sigprocmasksigpending捕捉信号内核空间与用…

three.js 之 入门篇 5之几何体的认知( 顶点创建矩阵、炫酷三角形科技物体、基础网格材质 material )

目录three.js 之 入门篇 5之几何体的认知01BufferGeometry设置顶点创建矩阵02 生产炫酷三角形科技物体03 常见的网格几何体 geometry04 基础网格材质 material04-1 初识别材质与纹理04-2 初识别材质与纹理 &#xff08; 平移、旋转 &#xff09;04-3 纹理显示设置&#xff08; …