结构体中的内存管理
注:本文使用的环境为:Ubuntu18.0/64位
C语言中的结构体
结构体是C语言中的一种构造数据类型,表示一堆数据的集合,其中的成员(member)的数据类型可以不同。
字节对齐原则
为了提高CPU的访问效率,结构体的存放有着字节对齐的原则,例如在我所使用的64位操作系统环境下,CPU一次从内存中读取8个字节的数据,若数据存在对齐,则能更好的访问他们。
CPU访问内存示意图
结构体的字节对齐原则:
结构体的起始位置为成员中所占内存最大成员的整数倍。
各成员的起始位置,为成员自身的整数倍,若不足则在前一成员后补空。
结构体的总长度为最大字节成员所占内存的整数倍,若不足则在末尾补空。
运行结果:
结构体的起始地址:0x7ffc9ac25f30
结构体的大小:24
a[2]的起始地址:0x7ffc9ac25f30
a[2]的大小:3
i的起始地址:0x7ffc9ac25f34
i的大小:4
c的起始地址:0x7ffc9ac25f38
c的大小:1
j的起始地址:0x7ffc9ac25f40
j的大小:8
结构体tt的内存示意图
内存优化方法
观察上面的内存示意图和代码运行结果,发现这样使用结构体使内存由大量的冗余,这时我们可以对其进行优化。
根据其字节对齐的原则我们可以有相应的优化方法:
调整结构体中成员的位置
使用强制对齐
使用位域
1、调整结构体成员位置
根据其存放时的对齐原则,我们合理的调整结构体中成员的位置可以更大化的利用内存。
我们将char型的c前置,使后面的成员对齐时不再补空。
运行结果:
结构体的起始地址:0x7ffc247ae6c0
结构体的大小:16
a的起始地址:0x7ffc247ae6c0
a的大小:3
i的起始地址:0x7ffc247ae6c4
i的大小:4
c的起始地址:0x7ffc247ae6c3
c的大小:1
j的起始地址:0x7ffc247ae6c8
j的大小:8
优化后的内存示意图
2、强制对齐
使用#pragma pack(n)命令可改变编译器的默认对齐方式,其中n为需对齐的字节,这里我们将使用#pragma pack(1)的强制对齐方式。* 更多#pragma pack(n)的信息
运行结果:
结构体的起始地址:0x7ffc6af64e30
结构体的大小:16
a的起始地址:0x7ffc6af64e30
a的大小:3
i的起始地址:0x7ffc6af64e33
i的大小:4
c的起始地址:0x7ffc6af64e37
c的大小:1
j的起始地址:0x7ffc6af64e38
j的大小:8
优化后的内存示意图
3、使用位域
在实际使用中,对于结构体中的成员我们有时可能不需要用到它的整个内存,有可能只需要用到一个位,比如控制一个开关,我们可能只需要用到0或者1就能控制。* 更多位域信息
运行结果:
结构体的起始地址:0x7ffc9324802c
结构体的大小:12
a的起始地址:0x7ffc9324802c
a的大小:3
j的起始地址:0x7ffc93248030
j的大小:8
注意:在使用位域后不能直接查看成员的地址。
优化后内存示意图
一定需要优化吗?
观察上节中使用位域优化的例子可以发现,优化后内存占用确实是少了,但是CPU访问内存时,仍需访问两次,可见性能并没有提升,在实际应用中我们应视情况来合理的优化结构体内存的使用。比如在一组操作中,两个操作是同时进行或者使用的次数较多时,可选择将其放在同一中,以达到提升效率的目的。
参考资料:
封面:Pexels 上的 Rodolfo Clix 拍摄的图片
版权声明: 本文为 InfoQ 作者【Liuchengz.】的原创文章。
原文链接:【http://xie.infoq.cn/article/371fee8c49e498539e4eb23e7】。文章转载请联系作者。
评论