目录
1、 结构体的内存对齐
1.1、对齐规则
1.2、练习1、练习2(演示对齐规则1、2、3、4)
2、为什么存在内存对齐
2.1、平台原因(移植原因)
2.2、性能原因
2.3、那么如何即满足对齐,又要节省空间呢?
3、修改默认对齐数
4、结构体传参
4.1、将结构体传到函数print中
4.2、将地址传到函数print中
4.3、区别
5、结构体实现位段
5.1、什么是位段
5.2、位段的内存分配
5.3、注意事项
6、谢谢观看
上一篇博客,写了结构体变量的创建、初始化和声明等内容,今天的这篇博客来带大家深入理解结构体的知识点。希望大家多多支持。
正文
1、 结构体的内存对齐
首先,抛一个问题:结构体的大小如何计算?
要知道这个题的答案,首先要了解结构体内存对齐。
1.1、对齐规则
1、结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2、其他成员变量要对齐到某一个数字(对齐数)的整数倍的地址处
3、结构体总大小为最大对齐数的整数倍
4、如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数中的最大值的整数倍
偏移量:字节与结构体变量开始存放位置之间相偏移的值
对齐数:编译器默认的一个对齐数与该成员变量大小相比 二者之中取。较小值
VS中默认的一个对齐数是 8
Linux中gcc 没有默认对齐数,对齐数就是成员自身的大小
最大对齐数:结构体中每个成员变量都有一个对齐数,所有对齐数中最大的数
1.2、练习1、练习2(演示对齐规则1、2、3、4)
求结构体的大小
练习1、
(演示对齐规则1、2 、3)
找对齐数:
对齐数: 编译器默认的一个对齐数与该成员变量大小 相比 二者之中取较小值。
c1 的对齐数是 1
i 的对齐数是4
c2 的对齐数是1
对齐规则1: 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
如下图:第一个成员变量c1 放在偏移量为0的位置
对齐规则2:其他成员变量要对齐到其对齐数的整数倍的地址处
成员 i 的对齐数是 4,i 从偏移量为4的倍数的位置开始存放,按本题即从偏移量为4的位置开始,向后存放4个字节。
成员 c2 的对齐数是 1, c2 从偏移量为1的倍数的位置开始存放,按本题即从偏移量为8的位置开始,向后存放1个字节。
对齐规则3: 结构体总大小为最大对齐数的整数倍
结构体中三个成员的对齐数分别为 1、4、1,则最大对齐数是 4
那么结构体总大小为 4 的整数倍
由上图,三个成员已经占了9个字节的空间,所以不能少于4的2倍为8
则结构体总大小为 4*3=12, 4的3倍
练习2、
(演示对齐规则4)
对齐规则4: 如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数中的最大值的整数倍
对于 struct S2 (内嵌结构体),其结构体总大小为2*8=16
内嵌结构体的最大对齐数是 8
内嵌结构体的最大对齐数是 8 ,则在结构体S3中该结构体的对齐数为8 ,大小为16
由上图,该结构体的大小为 4*8=32
2、为什么存在内存对齐
2.1、平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
举例说明:有些平台上结构体成员中 int类型的数据只能存在4的倍数的内存中,此时就需要有内存对齐。
2.2、性能原因
数据结构(特别是栈)应该尽可能的在自然边界上对齐。原因:为了访问未对齐的内存 ,处理器需要做两次内存访问;而对齐的内存访问只需要一次。
例如:
在32为平台下,一次访问4个字节,成员i 在对齐的情况下能被一次读完。
不对齐的情况下(按顺序存放)
所以说,内存对齐损耗了空间,但节省了时间,结构体的内存对齐是拿空间来换取时间的做法。
2.3、那么如何即满足对齐,又要节省空间呢?
请看下面的例子:(两个结构体中只是更改了成员的顺序)
struct S1中 两个占空间小的char 类型的成员分散排列。
而 struct S2中 两个占空间小的char 类型的成员集中在一起排列。
所以要即满足对齐,又要节省空间的方法是:让占用空间小的成员尽量集中在一起。
3、修改默认对齐数
使用 #pragma 这个预处理命令,可以修改编译器的默认对齐数。
具体使用:
设置默认对齐数为1,相当于不对齐的情况,所占字节是所有成员的字节大小。
结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。
4、结构体传参
结构体传参可以传结构体,也可以传地址。但我们首选传地址。
4.1、将结构体传到函数print中
4.2、将地址传到函数print中
4.3、区别
传结构体:在传结构体时需要创建临时结构体来储存,如果结构体中有成员占内存过大,会在传递时产生时间和空间的巨大开销。
正经解释:
函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销较大,所以会导致性能下降。
故:结构体传参的时候,要传结构体的地址。
5、结构体实现位段
结构体具有实现位段的能力。
5.1、什么是位段
位段成员必须是int、unsigned int或 signed int,在C99中位段成员类型也可以选择其他类型。
基本形式:位段成员名后面有一个冒号和一个数字。数字代表该成员所占的bit位数。
这里的A就是位段类型。
5.2、位段的内存分配
- 位段的空间上是按照以4个字节或1个字节的方式来开辟的。
- 位段涉及很多的不确定因素,是不能跨平台的。
详细开辟方式如结构体。
5.3、注意事项
不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段成员。
如下: