当前位置:   article > 正文

【go从入门到精通】struct结构体的内存布局问题

【go从入门到精通】struct结构体的内存布局问题

作者简介:


        高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注)

-------------------------------------------------------------------------------------------------------------------------------

        如果你之前学习过其他语言,比如C,C++,Java,你一定会非常在意于变量或者实例化的对象所占的内存空间布局,因此,go语言作为高效开发语言也必须掌握其内存布局,这里我接着上一篇文章继续分享给大家:

基础知识

在尝试了解结构体大小如何以及为何变化之前,让我们先回顾一下一些基础知识:

  1. 系统架构:如何定义字节大小和内存中的最大对齐方式。 32 位系统和 64 位系统之间的主要区别之一在于字节大小 假设有一个整型int变量:
    32 位系统为 4 个字节,
    64 位系统为 8 个字节。
  2. Golang 的原始类型大小:每个结构体的字段需要占用多少字节大小。你可以在此处检查支持的类型中的不同类型大小,它还包含字节大小和架构相关的解释。
  3. 内存布局:Go语言的内存布局主要分为栈和堆两部分。栈是用于存储局部变量和函数调用的数据结构,它在编译时就确定了大小。堆是用于动态分配内存的数据结构,它的大小是在运行时确定的。
  4. 字节对齐:字节对齐是指在内存中存储数据时,数据按照字节对齐的原则进行排列的过程。字节对齐的目的是为了提高内存的访问效率,避免因为访问未对齐的数据而造成额外的开销。

在Go语言中,结构体的成员变量会按照字节对齐的原则进行排列。具体的字节对齐规则如下:

  • Go语言中的基本数据类型(如 int、float、bool)的对齐值是它的大小,例如 int8 对齐值为 1,int16 对齐值为 2,int32 对齐值为 4,int64 对齐值为 8。
  • 结构体的对齐值是其成员变量中对齐值最大的那个,例如结构体中有一个 int16 类型的成员变量和一个 int32 类型的成员变量,那么结构体的对齐值就是 4。
  • 结构体的大小是其对齐值的整数倍,不会小于对齐值。

需要注意的是,Go语言的内存布局和字节对齐可能会受到不同编译器和操作系统的影响,具体的规则可能会有所不同。在实际开发中,可以使用 unsafe.Sizeof 函数和 unsafe.Alignof 函数来获取变量的大小和对齐值。

unsafe包

        接着上面的继续说,在 Go 语言中,你可以使用 unsafe 包来定义字段的内存布局和字节大小。unsafe.Alignof 函数可以用来获取类型的自然对齐方式,unsafe.Sizeof 函数可以用来获取类型的字节大小。

例如,你可以定义一个结构体,并使用这些函数来查看在 32 位和 64 位系统中的内存布局和大小:

  1. package main
  2. import (
  3. "fmt"
  4. "unsafe"
  5. )
  6. type MyStruct struct {
  7. myBool bool // 1 byte
  8. myFloat float64 // 8 bytes
  9. myInt int32 // 4 bytes
  10. }
  11. func main() {
  12. fmt.Printf("Size of MyStruct is: %d bytes\n", unsafe.Sizeof(MyStruct{}))
  13. fmt.Printf("Align of MyStruct is: %d bytes\n", unsafe.Alignof(MyStruct{}))
  14. fmt.Printf("Size of int is: %d bytes\n", unsafe.Sizeof(int(0)))
  15. fmt.Printf("Align of int is: %d bytes\n", unsafe.Alignof(int(0)))
  16. fmt.Printf("Size of bool is: %d bytes\n", unsafe.Sizeof(bool(false)))
  17. fmt.Printf("Align of bool is: %d bytes\n", unsafe.Alignof(bool(false)))
  18. fmt.Printf("Size of float32 is: %d bytes\n", unsafe.Sizeof(float64 (0)))
  19. fmt.Printf("Align of float32 is: %d bytes\n", unsafe.Alignof(float64 (0)))
  20. }

输出:

当使用unsafe.Sizeof函数实例化并检查结构MyStruct的大小时,它给出 24 作为输出。

a := MyStruct{}

fmt.Println(unsafe.Sizeof(a)) //24

MyStruct 的内存分配

绿色空间是使用的字节,红色空间是为了“完成”内存中的字而添加的填充,因此下一个字段可以从字大小的倍数的偏移量开始。在前面的示例中,我们看到有 11 个字节的填充被浪费了,这是可以优化的。

为了优化前面的示例,我们必须重新排列结构体的字段,以最大限度地减少分配结构体所需的填充字节量。由于myIntmyBool 字段的总大小小于字大小,因此它们可以彼此相邻(顺序无关紧要),从而将所需的填充字节从 11 个减少到 3 个;所以结果是这样的:

  1. type MyStruct struct {
  2.  myFloat float64 // 8 bytes
  3.  myBool  bool    // 1 byte
  4.  myInt   int32   // 4 bytes
  5. }

当实例化并检查 MyStruct 结构的大小时,它给出 16 作为输出。少了 8 个字节!

  1. package main
  2. import (
  3. "fmt"
  4. "unsafe"
  5. )
  6. type MyStruct struct {
  7. myFloat float64 // 8 bytes
  8. myBool bool // 1 byte
  9. myInt int32 // 4 bytes
  10. }
  11. func main() {
  12. fmt.Printf("Size of MyStruct is: %d bytes\n", unsafe.Sizeof(MyStruct{}))
  13. fmt.Printf("Align of MyStruct is: %d bytes\n", unsafe.Alignof(MyStruct{}))
  14. fmt.Printf("Size of int is: %d bytes\n", unsafe.Sizeof(int(0)))
  15. fmt.Printf("Align of int is: %d bytes\n", unsafe.Alignof(int(0)))
  16. fmt.Printf("Size of bool is: %d bytes\n", unsafe.Sizeof(bool(false)))
  17. fmt.Printf("Align of bool is: %d bytes\n", unsafe.Alignof(bool(false)))
  18. fmt.Printf("Size of float32 is: %d bytes\n", unsafe.Sizeof(float64 (0)))
  19. fmt.Printf("Align of float32 is: %d bytes\n", unsafe.Alignof(float64 (0)))
  20. }
 

正如从内存分配图中看到的,在第二个示例中,仅通过对结构字段重新排序即可消除整行填充。对齐可以是 1、2、4 或 8。填充是用于填充变量以填充对齐的空间(基本上是浪费的空间)。

尝试找出如何再添加 3 个布尔字段并仍将大小保持为 16 字节。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/404504
推荐阅读
相关标签
  

闽ICP备14008679号