golang struct详解

Go struct详解

内存模型

go语言的结构体的内存模型,是连续的内存。

内存对齐

package main

import (
   "fmt"
   "unsafe"
)

type StructA struct {
   A bool
}

type StructB struct {
   A bool
   B int16
}

func main() {
   a := StructA{}
   fmt.Println(unsafe.Sizeof(a))

   b := StructB{}
   fmt.Println(unsafe.Sizeof(b))
}

因为结构体的内存模型是一连串连续的内存,所以

StructA是由一个int8类型组成的,会占用1个字节的内存空间

StructB是由一个int8和一个int16,那么它会占3个字节的内存空间吗?

答案是否定的,原因是go语言的结构体会进行内存对齐。

那么什么是内存对齐,为什么需要内存对齐?

我们已经知道,计算机里由许多总线,有数据总线、地址总线和控制总线。数据总线主要用来传输数据。数据总线就像一条高速公路,高速公路上有N条车道,而这条高速公路同一时刻,可以并行行驶N辆车辆。在计算机里,数据总线一次可以传输的数据量是由处理器决定的,比如在64位处理器中,一次就可以传输64bit的数据。CPU在运行时需要读取数据,通过地址总线找到数据存储的位置,再通过数据总线将数据传给CPU。

在计算机中,最小的存储单位是byte,每个byte都对应由一个内存地址。
当cpu读取内存时并不是以一个一个字节去读取和写入内存,而是通过一块一块的读取,块大小可以是2、4、6、8、16字节等大小。块的大小称为访问粒度。比如32位处理器块大小通常为4个字节。

这样做的目的是减少访问内存的次数,比如一个int32,存储在4个字节的内存中,如果一个地址一个地址的读取,需要读取4次,才能得到我们所需要的int32的值。

cpu在读取这个某个内存地址时,会把这个地址附近的一片地址(连续的8byte)的数据一次性都读出来。

比如在64位系统中,可以把内存想象成一个个连续的块组成,每个块由8个字节组成,当访问某个内存数据时,会将这个内存数据所在的块的内容都读取出来一并返回。

内存对齐尽量将一个变量的数据分配在一个块内,以减少cpu读取内存的次数。

比如一个int64的变量,如果没有进行内存对齐,int64的数据可能会存在两个不同的块中,那么cpu需要两个周期才能得到这个变量的值,通过内存对齐,将数据存储在同一个块呢,cpu只需要一个周期就可以得到需要的值。

Go语言内存对齐规则
默认对齐系数:

在不同平台上的编译器都有自己的默认对齐系数,常用平台的系数如下:

  • 32位: 4
  • 64位: 8
类型自身对齐系数:

通过unsafe.Alignof()可以获取到类型自身的对齐系数。

对齐值:

对齐值=min(编译器默认对齐系数,类型自身对齐系数)

比如64位系统中,类型为int8的对齐值为 min(8,1) = 1

结构体成员对齐规则:
  • 结构体的成员变量,第一个成员变量的偏移量为 0。往后的每个成员变量的对齐值必须为编译器默认对齐长度(#pragma pack(n))或当前成员变量类型的长度(unsafe.Sizeof)取最小值作为当前类型的对齐值其偏移量必须为对齐值的整数倍
  • 结构体本身,对齐值必须为编译器默认对齐长度(#pragma pack(n))或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值
  • 结合以上两点,可得知若编译器默认对齐长度(#pragma pack(n))超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

例如:

struct{
    A int8
    B int32
}
// 第一个成员A的偏移量为0,对齐值为1
// 第二个成员B的对齐值为4,偏移量必须为对齐值的整数倍,偏移量为4
// A-B之间为填充Padding
结构体内存对齐优化

通过优化结构体内成员的排列顺序,可以减少内存对齐中的padding数量,从而减少内存占用。

获取对齐值:unsafe.SizeOf()

获取偏移量:unsafe.Offsetof()

结构体的方法

访问结构体方法的规则:

  1. 结构体变量在访问方法时进行了隐式的转换
  2. 通过var声明的指针结构体,其值为nil,nil也可以访问指针接收者的方法,但不能对结构体对象做任何操作,nil也不能访问值类型接收者的方法

值接收者类型的选择:

  1. 需要修改值时,用指针类型的接收者
  2. 结构体比较大,用指针类型的接收者
  3. 保持风格统一

匿名字段和组合

空结构体

空结构体的size为0,不占用内存。

  • 作为方法接收者
  • 配合map实现集合 map[int]struct{},节省内存
  • 配合chan实现空通道,用于只通知但不传递具体的值的场景

结构体比较

在Go语言中,Go结构以有时候是不能直接比较,当其基本类型包含:slice,map,function时,是不能比较的,对于指针的比较,因为其指针地址是不一样的,如果要比较指针所代表的内容,可以使用reflect.DeepEqual()方法。

Reference

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值