美文网首页
Go的内存对齐问题

Go的内存对齐问题

作者: 绝望的祖父 | 来源:发表于2019-08-13 23:32 被阅读0次

我们都知道,在Go语言中,空的结构体不占用任何存储空间,比如:

func main() {
    a := A{}
    fmt.Printf("Size of a: %d", unsafe.Sizeof(a))
}

type A struct{}

运行结果:

Size of a: 0

但是,在某些情况下,以上结论并不是完全正确,让我们来看一个例子:

type MyStruct struct {
    Flag  uint32
    PropA uint64
    PropB uint64
    PropC uint64
    PropD struct{}
}

结构体MyStruct到底占用多少空间呢,在我64位的机器运行结果如下:

Size of mystruct: 40

类型MyStruct中包含的都是固定大小的字段,Flaguint32类型,占用4字节大小,PropA, PropB, PropC都是uint64类型,分别占用8字节大小,PropD是空结构体,不占用存储空间。因此理论上,该类型占用都总存储空间大小应该是: 8+8+8+4 = 28 字节,那为何程序编译后发现实际上占用了40个字节大小呢?

内存对齐

产生这种差异的原因在于内存对齐。Go语言规范要求,结构体字段的地址必须是内存对齐的。因此,在64位机器上,任何uint64字段的地址必须是8的倍数。实际上编译器看到的结构体像这个样子:

type MyStruct struct {
    Flag  uint32
    _     [4]byte // 由编译器添加用于内存对齐
    PropA uint64
    PropB uint64
    PropC uint64
    PropD struct{}
}

此时理论上结构体占用的内存大小应为:Flag(4 bytes) + PropA(8 bytes) + PropB(64 bytes) + PropC(64 bytes) + 4 bytes 用于对齐的空间 = 32 bytes。但是我们知道之前程序运行的结果是40 bytes,那么多的4 bytes空间去哪了?

为了说明这个问题,让我们首先重新排列一下MyStruct结构体的字段顺序:

type MyStruct struct {
    Flag  uint32
    PropD struct{}
    PropA uint64
    PropB uint64
    PropC uint64
}

此时检查结构体大小,发现结果为:

Size of mystruct: 32

嗯,看起来有进步。我们已经设法减少了MyStruct结构体的存储空间,为什么会这样?

空结构体

你可能已经猜到,额外的存储来自于字段PropD,它是一个类型为struct{}的字段。在结构体底部存在一个空结构体字段会导致它的存储空间增加,什么意思?

答案是,虽然空结构体字段不占用存储空间,但你可以合法地取得它的地址。也就是说,假设有如下类型:

type S struct {
    Foo struct{}
    Bar struct{}
}

var s S

取s.Foo的地址是完全有效的,因此,s.Foo的地址将超出结构体的末尾!虽然Go代码无法对此无效地址执行任何操作,但是它毕竟是一个指针,垃圾收集器必须回收它,这可能导致内存泄漏或垃圾收集器崩溃。

关于这个问题的详细讨论在issue 9401中,并在Go 1.5中进行了修复。简单来说,对于任何占用0大小空间的类型,像struct {}或者[0]byte这些,如果该类型出现在结构体末尾,那么我们就假设它占用1个字节的大小。

因此,对于之前的结构体MyStruct,它实际上看起来是这个样子:

type MyStruct struct {
    Flag  uint32
    PropA uint64
    PropB uint64
    PropC uint64
    // PropD struct{}
    PropD [1]byte
}

加上内存对齐,对编译器来说就是:

type MyStruct struct {
    Flag  uint32
    _     [4]byte // 编译器添加用于内存对齐
    PropA uint64
    PropB uint64
    PropC uint64
    // PropD struct{}
    PropD [1]byte
    _     [7]byte // 编译器添加,结构体整体也需要内存对齐
}

一共占用40个字节。

结论:不要在结构体定义的最后添加零大小的类型

相关文章

  • Go的内存对齐问题

    我们都知道,在Go语言中,空的结构体不占用任何存储空间,比如: 运行结果: 但是,在某些情况下,以上结论并不是完全...

  • 内存对齐

    本次主要讨论三个问题: 什么是内存对齐 内存对齐的好处 如何对齐 内存对齐 内存对齐是一种提高内存访问速度的策略。...

  • 结构体内存对齐

    对象内存对齐 探讨的问题 1.什么是内存对齐?2.为什么要做内存对齐?3.结构体内存对齐规则4.源码内存对齐算法 ...

  • 指针对其问题

    关于Go结构体内存大小的一点小知识 在go语言里我们经常使用struct作为数据存储,由于指针对齐问题,可能结构体...

  • C/C++内存对齐

    在面试或工作中,经常会遇到内存对齐的问题。这里结合我的理解谈一谈对内存对齐的理解。 1. 为什么要内存对齐,不对齐...

  • 内存对齐问题

    为什么要内存对齐 1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某...

  • Go语言——内存管理

    Go语言——内存管理 参考: 图解 TCMalloc Golang 内存管理 Go 内存管理 问题 内存碎片:避免...

  • 2.iOS底层学习之内存对齐

    学习了内存对齐之后的疑问?? 1.为啥要内存对齐?2.内存对齐的规则?3.内存对齐实例分析。 内存对齐的目的 上网...

  • iOS底层之内存对齐算法解析

    目前但凡一个iOS岗面试都会问个内存对齐问题,那么什么是字节对齐?成员变量对齐和对象内存对齐有什么区别?今天我来为...

  • 在 Go 中恰到好处的内存对齐

    原文地址:在 Go 中恰到好处的内存对齐 问题 在开始之前,希望你计算一下 Part1 共占用的大小是多少呢? 输...

网友评论

      本文标题:Go的内存对齐问题

      本文链接:https://www.haomeiwen.com/subject/sfpijctx.html