C语言深度理解结构体(内存对齐、位段、偏移量、柔性数组)

前言:

本文主要阐述c语言中对于结构体的深入理解,谈谈关于结构体占用内存是如何计算、如何理解位段、成员的偏移量以及柔性数组是什么、如何用,对于结构体的创建、引用等等不进行赘述。

内容主要分为三部分:
1 结构体里的偏移量、内存对齐
2 结构体里的位段
3柔性数组


1 结构体中的偏移量、内存对齐

在里了解内存对齐之前,要首先理解偏移量是什么?
关于偏移量:是我们建立结构体时里面每一个成员的位置相对于结构体首地址的距离的长度,单位是字节数。

举个例子:


我们建立一个结构体 A,然后创建成员int类型x,char 类型y ,int类型z。如果按照正常来说,这里面struct A结构体所占用内存是所有成员所占用的内存之和,也就是4+1+4=9个字节,但其实通过计算得出的是12个字节




为什么会这样呢?
这里引出一个offsetof函数(头文件<stdlib.h>),计算结构体每一个成员的偏移量:
那么我们来计算一下每一个成员的偏移量就大致知道为什么这三个成员所占的内存会达到12个字节


从测试结果可以看到,x距离结构体首地址是,也就说偏移量是0,说明x就是从首地址开始向后占据空间,也就是从0开始向后数4个字节,到达3,然后y的偏移量4,也就是说从首地址向后数5个字节数(0算一个字节),然后就是z偏移量是8,就是从第9个地址(相对于结构体首地址)开始向后数4个字节到达11,所以0到11总共12个字节,结构体A占12个字节数。

图解:

可以看到结构体A里面的成员是根据偏移量然后找到对应位置然后才占据内存的。中间的偏移量5、6、7就因此空出来了,由此可以计算结构体的所占内存大小。

那么计算的方法是什么呢?
1:结构体第一个成员,存放结构体变量开始位置的0偏移处(也就是说,第一个成员一定在0偏移处开始存放)
2:从第二个成员开始,都要对齐到对齐数的整数倍地址处(对齐数:成员自身大小和默认对齐数两者的较小值)(vs环境下默认对齐数是8)
3:结构体总大小,必须是最大对齐数的整数倍
4:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数整数倍处(是指所有成员当中的对齐数最大的那个)该结构体的大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍


解释两个概念:


默认对齐数:不同的平台的默认对齐数不一样,比如说vs2019的默认对齐数是8,vs2013的默认对齐数是4,linux下的没有默认对齐数。
默认对齐数要和每一个成员所占的内存大小进行比较,找出较小值就是这个成员的对齐数。
比如说int x自己占用的内存是4,而vs2019默认对齐数是8,两者较小值是4,所以int x的对齐数4

 


最大对齐数:就是所有成员中的对齐数中最大的数。
比如说结构体A的最大对齐数是4




例子:
例如我前面创建的结构体 A,根据第1条规则,结构体的第一个成员必须从偏移量0开始向后存放,第一个成员int x就从偏移量0开始向后4个字节到达偏移量3,,根据规则2,接着从第二个成员开始,其都要对齐到对齐数整数倍地址处,vs默认对齐数是8,char 所占字节1,那么默认对齐数较小值是1,所以从1的倍数地址开始,从4开始向后一个字节,然后是第三个成员int z,vs默认对齐数是8,int是4两者较小值是4,所以成员z从4的倍数开始存放,由于偏移量4被char y 存放 ,所以找到8(8是4的倍数)开始向后存放4个字节,到达11,然后找到最大对齐数4,然后计算最后一个成员到达的偏移量距离偏移量0总共字节数,根据规则3:结构体占的总字节数必须是最大对齐数的整数倍,若总字节数是4的倍数则该总字节数就是所占内存,即0-11是12个字节,12是4的倍数,所以总字节数就是12,倘若此时最后一个所占的偏移量是12,0-12总字节是13,13不是4的倍数,此时必须扩大到4的倍数,就是16,此时13-15的内存被空出来了,结构体所占总字节就是0-15为16个字节。

图解:

 


最后是如何修改默认对齐数:

下面是在vs2019编译器下是如何修改默认对齐数:


利用#pragma pack来进行修改和还原


为什么要有内存对齐?
大部分资料是这么说的:
1平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则出现硬件异常。
2性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问只需要一次访问。
总的来说
结构体的内存对齐是拿空间时间的做法。

 

 2 结构体里的位段
1 位段的成员必须是int、unsigned int 或signed int。
2 位段成员后面有一个冒号还有一个数字。

位段里面的内存分配:

位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
位段涉及很多不确定的因素,位段不是跨平台的,注意可移植的程序应该避免使用位段。



举个例子

 

以上结构体C成员有两个,分别是a和b,如果按照正常的去计算应该是占据2个字节,但是测试结果却是1个字节数:

这就是位段的作用。
”char a:2“ 中的2其实代表的是2比特位,当建立char类型的变量将开辟1个字节也就是8个比特位来存放数据,但是位段设立为2,意思其实是该变量a只占用两个比特位来存放数据,若存放数据大于2个比特位则取2个比特位的二进制数据(会发生截断):
例如说我赋值2给结构体类型C的成员A,那2变成2进制便是”10“占两个比特位,然后再赋值成员b为2,则按照位段分配给b是4个比特位,而我只用了2个比特位,则在内存中便是:




3 柔性数组
结构体中最后一个元素允许是未知大小的数组,叫做柔性数组成员
 1 柔性数组前面必须有成员
                                  2 在计算结构体大小时,柔性数组大小不计入总大小,只记录前面成员所占内存
3 柔性数组的大小用动态内存开辟的方法进行开辟内存

 

第二个成员b就是柔性数组。
计算一下未动态开辟内存的结构体大小


可以发现只计算了柔性数组前面一个成员的内存大小
 


进行动态内存开辟 

对含有柔性数组的结构体进行动态内存开辟,开辟10个元素给数组。
然后进行赋值和打印:

最后再计算开辟内存后的含有柔性数组的结构体大小:

测试结果:

可以发现柔性数组动态开辟后就类似于普通的数组。可以直接使用
可以发现进行动态内存开辟后的结构体大小并没有增加,还是计算的是柔性数组前面成员的大小。
 

  • 37
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
C语言中,结构体内存对齐是为了提高存储器的访问效率。结构体的成员在内存中的布局可能会按照一定规则进行对齐,以保证访问成员时的效率和正确性。 内存对齐是为了满足硬件对数据的访问要求,比如某些平台要求访问某些类型的数据必须从特定地址开始。此外,对齐也有助于减少内存碎片和提高内存的利用率。 在C语言中,默认情况下,结构体的成员按照其声明的顺序依次存放在内存中,但是编译器可能会在结构体中添加一些填充字节,以保证结构体的对齐要求。 编译器会根据结构体成员的类型和顺序来确定对齐方式,常见的对齐规则有以下几种: 1. 自然对齐:结构体成员按照其自身的大小进行对齐。例如,一个int类型的成员会按照4字节对齐。 2. 最大对齐:结构体成员按照其成员中最大类型的大小进行对齐。例如,一个结构体中有int和char类型成员,那么整个结构体会按照int类型的大小进行对齐。 3. 指定对齐:通过编译器提供的特定语法,可以手动指定结构体成员的对齐方式。 结构体的对齐方式可以通过编译器的相关选项进行配置,比如gcc编译器可以使用`__attribute__((aligned(x)))`来指定对齐方式,其中x表示对齐的字节数。 需要注意的是,结构体的对齐方式可能会因编译器、编译选项和目标平台而有所不同。因此,在编写跨平台代码时,应尽量避免依赖结构体的具体内存布局和对齐方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值