深度剖析结构体的基本结构与结构体在内存中的对齐方式

编程中存在多种自定义数据类型,主要包含结构体,联合体,枚举等,结构体可以说是运用比较广泛的一种自定义数据类型,也是编程语言中一种比较重要的数据结构,本文将围绕结构体这一自定义类型展开分析,从结构体的基本定义出发,进而探究结构体在内存中的存储方式,加深对结构体这一自定义类型的理解,结构体从表面结构看似乎是很简单明了,但当我们深入探究结构体时,就会发现结构体在内存中的存储方式的“另一面”。

结构体的基本定义:

定义语句如下

Struct  结构体类型名

        结构体成员类型1  成员名;

       结构体成员类型2   成员名;

       结构体成员类型3   成员名;

         ....

 };

如上图,我们定义了两个结构体类型,第一个结构体类型struct stu中成员1为20个元素的字符数组,成员名为name,第二个成员的类型为整形类型,成员名为age,第三个和第四个成员的类型也为整形类型,成员名分别为high和weight,第五个成员为20个元素的字符数组,成员名为number,可以该结构体可以用来表示学生的基本信息,s4,s5,s6表示结构体名,可直接将结构体s4,s5,s6进行初始化。同理,结构体struct S有两个成员,字符型数据n,与int类型x。

了解了结构体的基本定义后,那如何对结构体成员进行赋值呢?这里有两种方法。

我们先来看第一种方法

对struct stu s1结构体进行赋值,采用第一种方法,即一对一对应,每个结构体成员用逗号隔开,这种赋值方法简单明了,也清晰直观。zhangsan对应成员name[20],21对应成员age,200对应成员high,100对应成员weight,123456789对应成员number[20]。

第二种方法

采用.操作符,用.代表结构体本身,后面跟上结构体成员名,对其进行赋值,如上图,.age就代表结构体类型struct stu中的age成员,.age=18,将其赋值为18,以此类推,逐一对每个成员进行赋值。

此外,结构体定义中也可存在结构体嵌套定义的情况,我们来看下面这个例子

结构体K中嵌套了结构体类型struct S的结构体成员变量,以及类型为整形的成员W,10个元素的字符数组成员arr,这种情况下对结构体成员赋值也是一样的方法。

这里采用第一种赋值方法,一对一对结构体成员进行赋值,结构体成员用{ }进行表示,其他成员表示与之前表示方法相同。

对结构体成员调用printf函数,可以采用.操作符,对结构体成员进行逐个访问,如下图操作

结构体名.成员名可实现对结构体成员的访问。

说到结构体嵌套,除了上面的结构体的定义嵌套,还有另一种嵌套,是结构体自引用,即结构体定义中嵌套结构体指针的情况,如下图所示:

struct Node的一个成员为struct Node*next指针,即结构体中包含了指向下一个结构体的指针,这种指针嵌套可以实现链式结构,每个结构体都具有指向下一个结构体的指针,可以实现结构体的相互连接。

对结构体成员赋值之后,可以通过printf函数将结构体成员逐一打印出来,可以采用.操作符,对结构体成员逐一访问,如下图调用print函数实现将struct S结构体成员逐一打印

这里print函数的参数为结构体变量名,通过结构体变量名进行传参,可知建立形参所需的内存空间是很大的,因为要建立arr一千个元素的堆栈空间,那有没有更节省空间的方法呢?答案是肯定的,可以将结构体指针作为函数参数,指针作为函数参数,指针只占4个或8个字节,相比结构体名传参,大大节省了内存空间,指针指向结构体成员,用->表示。

这里通过传址调用print函数,不仅实现了逐一打印结构体成员,也大大节省了函数形参建立所需的内存空间。

接下来,我们来更进一步地分析结构体在内存中的存储方式,结构体采用内存对齐的方式在内存中进行存储,这里涉及了对齐数的概念,我们以VS环境为例,VS环境默认的对齐数是8,这里需将结构体成员的字节大小逐一与8进行比较,取较小的数作为该成员的对齐数,我们可以将结构体在内存中的存储形象表示为下面这种形式

012345678910111213141516171819

以20个字节大小的内存为例,各个数字分别代表该地址相对第一个地址的偏移量,第一个结构体成员必须存放在偏移量为0的地址处,第二个结构体成员至最后一个结构体成员的地址需存放在该成员对齐数的整数倍的地址处,存放完成之后,结构体的大小还需满足是结构体成员对齐数的整数倍。下面我们来举一个具体例子来分析。

结构体S1成员1类型为double类型,大小为8个字节,与默认编译器的对齐数8一致,故对齐数取8,成员2类型为char类型,大小为1个字节,与8比较,故对齐数取1,成员3类型为int类型,大小为4个字节,与8比较,故对齐数取4。接下来,我们来讨论该结构体在内存中的存放。

07812131415

由上述可知,double类型的a存放在0~7位置,为8个字节,char需存放在其对齐数的整数倍的地址处,对齐数为1,故char c存放在地址为8的位置,同理,b需存放在其对齐数4的倍数的地址处,故b存放在地址为12的位置,占4个字节,整个结构体从0~15为16个字节,刚好为最大对齐数4的整数倍,因此,该结构体的大小为16个字节。同理,可分析得出S2结构体为32字节。

#pragma pack()指令可对系统默认对齐数进行修改

如上图,将默认对齐数修改为1,则不难分析出,该结构体在系统连续存放,因此大小为1+4+1=6个字节。

此外,为节省内存空间,结构体还引入了位段的概念,其大小可以通过分析成员的位分配来进行计算。

位段表示采用成员名:位大小;进行表示,如上图所示,其内存存储方式采取一个字节内可存储多个成员,在字节大小允许的情况下,若字节大小不够,则需重新开辟一个新的int来存储。

03456781732

首先,需存储成员a,a占用2个bit位,0~7为一个字节,从左往右存储还是从右往左存储没有一个统一的标准,我们假设从右往左存储,则6~7为a的存储空间,3~5为b的存储空间,由于b前面仅有两个比特位,c占10个比特位,显然不够,因此只能丢弃1~2两个比特位,c存储在8~17这十个字节,同理,17~32仅有16个bit位,d需要30个bit位,显然不够,因此需开辟新的内存空间,大小为4个字节,c存放在32之后的30个比特位,因此,结构体s的大小为4+4=8个字节。

结语

无论是简单的数据聚合,还是复杂的数据结构实现,结构体都能发挥重要作用。本文我们主要介绍了结构体的基本概念以及定义,在内存中对齐方式以及位段的相关概念,希望能为大家理解和使用结构体提供帮助。结构体虽然只是众多工具的一个,但它无疑是构建健壮、清晰代码的重要基石!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值