go int 最大值_一文看懂Go内存对齐

aee58044801b846518d4419ce3aee77a.png
本篇文章介绍Go是如何应用内存对齐,来分配变量空间

先想一个问题,这里有一个结构体T,如何算出该结构体占用的内存大小,以及各个字段在内存如何分布的?Go语言是如何进行内存对齐的?

type T struct {
    a bool
    b int8
    c uint16
    d uint32
    e int64
    f bool
}

要想解答这个问题,要先了解两个概念

类型尺寸

go白皮书只对以下类型占用的内存大小进行了明确规定.

type                       size in bytes
byte, uint8, int8          1
uint16, int16              2
uint32, int32, float32     4
uint64, int64, float64, complex64     8
complex128                 16
uint              either 32 or 64 bits 
int               same size as uint
uintptr           an unsigned integer large enough to store the uninterpreted bits of a pointer value

uint和int类型的大小取决于编译器和系统架构,通常32位架构为4字节,64位架构为8字节

uintptr 取决于编译器实现,但必须保证一定能够存下任一个内存地址.

除了上述明确规定的,白皮书没有对其他种类的类型尺寸进行明确规定.

类型尺寸获取代码:

func main(){
       var i bool = true
    var j int = 40
    fmt.Println("bool-size", unsafe.Sizeof(i))
    fmt.Println("int-size", unsafe.Sizeof(j))
}

输出结果:

bool-size 1
int-size 8

bool类型的大小占用1字节

int类型的大小占用8字节,因为我是64位系统,如果你是32位的输出会是4

如果仅仅按照类型尺寸的规则,来累加计算结构体T中每个字段的存储大小,发现和实际存储的大小不一样的.因为Go在分配结构体内存的时候,还需要参照对齐保证.

对齐保证

对齐保证也称为值地址对齐保证.也就是在分配变量存储位置的时候,不是随便分配的,是按照对齐保证的整数倍来分配内存地址

go白皮书仅仅规定了对齐保证的基本要求

The following minimal alignment properties are guaranteed:

1. For a variable `x` of any type: `unsafe.Alignof(x)` is at least 1.
2. For a variable `x` of struct type: `unsafe.Alignof(x)` is the largest of all the values `unsafe.Alignof(x.f)` for each field `f` of `x`, but at least 1.
3. For a variable `x` of array type: `unsafe.Alignof(x)` is the same as the alignment of a variable of the array's element type.

翻译版本

1. 对于任何类型的变量x,unsafe.Alignof(x)最小为1
2. 对于结构体类型变量x,unsafe.Alignof(x)的数值是结构体中所有字段的对齐保证中的最大值( the largest ),作为结构体的对齐保证,最小为1.
3. 对于数组类型变量x, unsafe.Alignof(x)的结果和数组元素的类型的对齐保证一致.

Go只是规定了对齐保证的基本规则,但是对于不同编译器不同的架构甚至于同一个编译器的不同版本,实现的对齐保证都会有一定的差异,了解规则即可.

使用的go1.15的标准编译器实现的对齐保证如下:

类型种类                    对齐保证(字节数)
------------------------------------------------------                                
bool, byte, uint8, int8    1
uint16, int16              2
uint32, int32              4
float32, complex64         4
数组                       取决于元素类型
结构体类型                  取决于各个字段类型
其它类型                    一个自然字的尺寸.在32位的架构上为4字节,在64位的架构上为8字节。

每个类型有两个对齐保证.

  • 当它被用做结构体类型的字段类型时的对齐保证称为此类型的字段对齐保证
  • 其它情形的对齐保证称为此类型的一般对齐保证

对齐保证获取代码:

func main(){
       var i bool = true
    var j int = 40
    fmt.Println("bool-align", unsafe.Alignof(i)) //一般对齐保证
    fmt.Println("int-align", unsafe.Alignof(j))

    type temp struct {
        a bool
        b int
    }
    var tmp = temp{}
    fmt.Println("a", unsafe.Alignof(tmp.a)) //字段对齐保证
    fmt.Println("b", unsafe.Alignof(tmp.b))
}

输出结果:

bool-align 1
int-align 8
a 1
b 8

不管是字段对齐保证还是一般对齐保证,获取的方法都是一样的,不需要刻意区分.

实例应用

内存对齐遵循的规则

变量的存储起始地址一定是对齐保证的整数倍
变量的大小是对齐保证的整数倍.所有的类型都要遵守这一规则.
package main

import (
    "fmt"
    "unsafe"
)
type T struct {
    a bool
    b int8
    c uint16
    d uint32
    e int64
    f bool
}

func main(){
    var t = T{}

    fmt.Println("t占用的实际内存大小:", unsafe.Sizeof(t), "字节,结构体对齐保证:", unsafe.Alignof(t))
    fmt.Println("a:", unsafe.Sizeof(t.a), "字节,字段对齐保证:", unsafe.Alignof(t.a), ",偏移地址:", unsafe.Offsetof(t.a))
    fmt.Println("b:", unsafe.Sizeof(t.b), "字节,字段对齐保证:", unsafe.Alignof(t.b), ",偏移地址:", unsafe.Offsetof(t.b))
    fmt.Println("c:", unsafe.Sizeof(t.c), "字节,字段对齐保证:", unsafe.Alignof(t.c), ",偏移地址:", unsafe.Offsetof(t.c))
    fmt.Println("d:", unsafe.Sizeof(t.d), "字节,字段对齐保证:", unsafe.Alignof(t.d), ",偏移地址:", unsafe.Offsetof(t.d))
    fmt.Println("e:", unsafe.Sizeof(t.e), "字节,字段对齐保证:", unsafe.Alignof(t.e), ",偏移地址:", unsafe.Offsetof(t.e))
    fmt.Println("f:", unsafe.Sizeof(t.f), "字节,字段对齐保证:", unsafe.Alignof(t.f), ",偏移地址:", unsafe.Offsetof(t.f))
    fmt.Println(uintptr(unsafe.Pointer(&t)))
}

输出结果:

t占用的实际内存大小: 24 字节,结构体对齐保证: 8
a: 1 字节,字段对齐保证: 1 ,偏移地址: 0
b: 1 字节,字段对齐保证: 1 ,偏移地址: 1
c: 2 字节,字段对齐保证: 2 ,偏移地址: 2
d: 4 字节,字段对齐保证: 4 ,偏移地址: 4
e: 8 字节,字段对齐保证: 8 ,偏移地址: 8
f: 1 字节,字段对齐保证: 1 ,偏移地址: 16
824634378592

如果按照上面输出的字段大小累加(1+1+2+4+8+1 = 17字节)明显和实际内存大小24字节不符合.现在来一步步解析上面的结果是怎么出来的.

53bc9367222e2f1a16d04d28ad2eefac.png

以此类推

592cdc15d943b7ee8b0e8e921fb79239.png

最后一个变量f完美分配完成,但是这个时候整个结构体类型还需要满足一个条件,类型的大小需要是对齐保证的整数倍.

结构体的对齐保证是所有字段对齐保证中的最大值,这里为8.而分配的内存大小只有17字节,不满足整数倍的关系.需要填充7字节,保证这个关系

b5324fcb7662b0312edf897555276fb7.png

最终结构体t的内存分配完成为24字节,符合我们的打印输出

结构体在内存中完整的存储图示:

d4ef1ced288cba7430b17b159f7c833e.png

举一反三:这里把结构体T中c和d字段的位置替换下,定义结构体R,再来验证下上面分析规则.

package main

import (
    "fmt"
    "unsafe"
)
type R struct {
    a bool
    b int8
    d uint32
    c uint16
    e int64
    f bool
}

func main(){
    var r = R{}
    fmt.Println("r占用的实际内存大小:", unsafe.Sizeof(r), "字节,结构体对齐保证:", unsafe.Alignof(r))
    fmt.Println("a:", unsafe.Sizeof(r.a), "字节,字段对齐保证:", unsafe.Alignof(r.a), ",偏移地址:", unsafe.Offsetof(r.a))
    fmt.Println("b:", unsafe.Sizeof(r.b), "字节,字段对齐保证:", unsafe.Alignof(r.b), ",偏移地址:", unsafe.Offsetof(r.b))
    fmt.Println("d:", unsafe.Sizeof(r.d), "字节,字段对齐保证:", unsafe.Alignof(r.d), ",偏移地址:", unsafe.Offsetof(r.d))
    fmt.Println("c:", unsafe.Sizeof(r.c), "字节,字段对齐保证:", unsafe.Alignof(r.c), ",偏移地址:", unsafe.Offsetof(r.c))
    fmt.Println("e:", unsafe.Sizeof(r.e), "字节,字段对齐保证:", unsafe.Alignof(t.e), ",偏移地址:", unsafe.Offsetof(r.e))
    fmt.Println("f:", unsafe.Sizeof(r.f), "字节,字段对齐保证:", unsafe.Alignof(r.f), ",偏移地址:", unsafe.Offsetof(r.f))
    fmt.Println(uintptr(unsafe.Pointer(&r)))
}

输出结果:

r占用的实际内存大小: 32 字节,结构体对齐保证: 8
a: 1 字节,字段对齐保证: 1 ,偏移地址: 0
b: 1 字节,字段对齐保证: 1 ,偏移地址: 1
d: 4 字节,字段对齐保证: 4 ,偏移地址: 4
c: 2 字节,字段对齐保证: 2 ,偏移地址: 8
e: 8 字节,字段对齐保证: 8 ,偏移地址: 16
f: 1 字节,字段对齐保证: 1 ,偏移地址: 24
824634378616

32d0e9a5a13c14b3a5b5726b324a2434.png

我们发现相同的字段数量和类型的结构体,字段位置的不同结构体T(24字节)和R(32字节)在内存中的实际存储大小是不同的.

在构造结构体时候,占用较大存储空间的字段后写比先写,占用的空间更小.

总结

这里主要是验证内存对齐规则对实际存储空间的影响,并验证了内存对齐的规则.平时工作中不需要关心值地址的对齐保证,编译器已经自动完成了相关的工作.除非打算优化下内存消耗.特别是定义结构体时,可以参照下上面的结论.

源码地址:

https://github.com/gofish2020/zeus/tree/main/source/memoryalign

参考资料:

https://golang.google.cn/ref/spec#Size_and_alignment_guarantees

https://golang.google.cn/ref/spec#Numeric_types

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值