一文弄懂:【Go】内存中的结构体

开发 前端
在数据存储上来讲,结构体和数组没有太大的区别. 只不过结构体的各个字段(元素)类型可以相同,也可以不同,所以只能通过字段的相对偏移量进行访问.

[[441160]]

结构体

所谓结构体,实际上就是由各种类型的数据组合而成的一种复合数据类型.

在数据存储上来讲,结构体和数组没有太大的区别. 只不过结构体的各个字段(元素)类型可以相同,也可以不同,所以只能通过字段的相对偏移量进行访问. 而数组的各个元素类型相同,可以通过索引快速访问,实际其本质上也是通过相对偏移量计算地址进行访问.

因为结构体的各个字段类型不同,有大有小,而结构体在存储时通常需要进行内存对齐,所以结构体在存储时可能会出现"空洞",也就是无法使用到的内存空间.

在之前的Go系列文章中,我们接触最多的结构体是reflect包中的rtype,可以说已经非常熟悉.

type rtype struct { 
    size       uintptr 
    ptrdata    uintptr // number of bytes in the type that can contain pointers 
    hash       uint32  // hash of type; avoids computation in hash tables 
    tflag      tflag   // extra type information flags 
    align      uint8   // alignment of variable with this type 
    fieldAlign uint8   // alignment of struct field with this type 
    kind       uint8   // enumeration for C 
    equal      func(unsafe.Pointer, unsafe.Pointer) bool 
    gcdata     *byte   // garbage collection data 
    str        nameOff // string form 
    ptrToThis  typeOff // type for pointer to this type, may be zero 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

在64位程序和系统中占48个字节,其结构分布如下:

在Go语言中,使用reflect.rtype结构体描述任何Go类型的基本信息.

在Go语言中,使用reflect.structType结构体描述结构体类别(reflect.Struct)数据的类型信息,定义如下:

// structType represents a struct type. 
type structType struct { 
    rtype 
    pkgPath name 
    fields  []structField // sorted by offset 

 
// Struct field 
type structField struct { 
    name        name    // name is always non-empty 
    typ         *rtype  // type of field 
    offsetEmbed uintptr // byte offset of field<<1 | isEmbedded 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

在64位程序和系统中占80个字节,其结构分布如下:

在之前的几篇文章中,已经详细介绍了类型方法相关内容,如果还未阅读,建议不要错过:

  • 再谈整数类型
  • 深入理解函数
  • 内存中的接口类型

在Go语言中,结构体类型不但可以包含字段,还可以定义方法,实际上完整的类型信息结构分布如下:

当然,结构体是可以不包含字段的,也可以没有方法的.

环境

OS : Ubuntu 20.04.2 LTS; x86_64 
Go : go version go1.16.2 linux/amd64 
  • 1.
  • 2.

声明

操作系统、处理器架构、Go版本不同,均有可能造成相同的源码编译后运行时的寄存器值、内存地址、数据结构等存在差异。

本文仅包含 64 位系统架构下的 64 位可执行程序的研究分析。

本文仅保证学习过程中的分析数据在当前环境下的准确有效性。

代码清单

在Go语言中,结构体随处可见,所以本文示例代码中不再自定义结构体,而是使用Go语言中常用的结构体用于演示.

在 命令行参数详解 一文中,曾详细介绍过flag.FlagSet结构体.

本文,我们将详细介绍flag.FlagSet和reflect.Value两个结构体的类型信息.

package main 
 
import ( 
  "flag" 
  "fmt" 
  "reflect" 

 
func main() { 
  f := flag.FlagSet{} 
 
  Print(reflect.TypeOf(f)) 
  Print(reflect.TypeOf(&f)) 
 
  _ = f.Set("hello""world"
  f.PrintDefaults() 
  fmt.Println(f.Args()) 
 
  v := reflect.ValueOf(f) 
  Print(reflect.TypeOf(v)) 
  Print(reflect.TypeOf(&v)) 
 
  Print(reflect.TypeOf(struct{}{})) 

 
//go:noinline 
func Print(t reflect.Type) { 
  fmt.Printf("Type = %s\t, address = %p\n", t, t) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

运行

从运行结果可以看到:

  • 结构体flag.FlagSet的类型信息保存在0x4c2ac0地址处.
  • 结构体指针*flag.FlagSet的类型信息保存在0x4c68e0地址处.
  • 结构体reflect.Value的类型信息保存在0x4ca160地址处.
  • 结构体指针*reflect.Value的类型信息保存在0x4c9c60地址处.
  • 匿名结构体struct{}{}的类型信息保存在0x4b4140地址处.

内存分析

在main函数入口处设置断点进行调试.我们先从简单的结构体开始分析.

匿名结构体struct{}

该结构体既没有字段,也没有方法,其类型信息数据如下:

  • rtype.size = 0x0 (0)
  • rtype.ptrdata = 0x0 (0)
  • rtype.hash = 0x27f6ac1b
  • rtype.tflag = tflagExtraStar | tflagRegularMemory
  • rtype.align = 1
  • rtype.fieldAlign = 1
  • rtype.kind = 0x19 (25) -> reflect.Struct
  • rtype.equal = 0x4d3100 -> runtime.memequal0
  • rtype.gcdata = 0x4ea04f
  • rtype.str = 0x0000241f -> "struct {}"
  • rtype.ptrToThis = 0x0 (0x0)
  • structType.pkgPath = 0 -> ""
  • structType.fields = []

这是一个特殊的结构体,没有字段,没有方法,不占用内存空间,明明定义在main包中,但是包路径信息为空,存储结构分布如下:

好神奇的是,struct{}类型的对象居然是可以比较的,其比较函数是runtime.memequal0,定义如下:

func memequal0(p, q unsafe.Pointer) bool { 
    return true 

  • 1.
  • 2.
  • 3.

也就是说,所有的struct{}类型的对象,无论它们在内存的什么位置,无论它们是在什么时间创建的,永远都是相等的.

细细品,还是蛮有道理的.

结构体类型flag.FlagSet

结构体flag.FlagSet包含8个字段,其类型信息占用288个字节.

  • rtype.size = 0x60 (96)
  • rtype.ptrdata = 0x60 (96)
  • rtype.hash = 0x644236d1
  • rtype.tflag = tflagUncommon | tflagExtraStar | tflagNamed
  • rtype.align = 8
  • rtype.fieldAlign = 8
  • rtype.kind = 0x19 (25) -> reflect.Struct
  • rtype.equal = nil
  • rtype.gcdata = 0x4e852c
  • rtype.str = 0x32b0 -> "flag.FlagSet"
  • rtype.ptrToThis = 0x208e0 (0x4c68e0)
  • structType.pkgPath = 0x4a6368 -> "flag"
  • structType.fields.Data = 0x4c2b20
  • structType.fields.Len = 8 -> 字段数量
  • structType.fields.Cap = 8
  • uncommonType.pkgpath = 0x368 -> "flag"
  • uncommonType.mcount = 0 -> 方法数量
  • uncommonType.xcount = 0
  • uncommonType.moff = 208
  • structType.fields =
[   
  { 
      name        = 0x4a69a0 -> Usage 
      typ         = 0x4b0140 -> func() 
      offsetEmbed = 0x0 (0) 
  },   
  { 
      name        = 0x4a69a0 -> name 
      typ         = 0x4b1220 -> string 
      offsetEmbed = 0x8 (8) 
  },   
  { 
      name        = 0x4a704a -> parsed 
      typ         = 0x4b0460 -> bool 
      offsetEmbed = 0x18 (24) 
  },   
  { 
      name        = 0x4a6e64 -> actual 
      typ         = 0x4b4c20 -> map[string]*flag.Flag 
      offsetEmbed = 0x20 (32) 
  },   
  { 
      name        = 0x4a6f0f -> formal 
      typ         = 0x4b4c20 -> map[string]*flag.Flag 
      offsetEmbed = 0x28 (40) 
  },   
  { 
      name        = 0x4a646d -> args 
      typ         = 0x4afe00 -> []string 
      offsetEmbed = 0x30 (48) 
  },   
  { 
      name        = 0x4a9450 -> errorHandling 
      typ         = 0x4b05a0 -> flag.ErrorHandling 
      offsetEmbed = 0x48 (72) 
  },   
  { 
      name        = 0x4a702f -> output 
      typ         = 0x4b65c0 -> io.Writer 
      offsetEmbed = 0x50 (80) 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

从以上数据可以看到,结构体flag.FlagSet类型的数据对象,占用96字节的存储空间,并且所有字段全部被视为指针数据.

flag.FlagSet类型的对象不可比较,因为其rtype.equal字段值nil. 除了struct{}这个特殊的结构体类型,估计是不容易找到可比较的结构体类型了.

从以上字段数据可以看到,FlagSet.parsed字段的偏移量是24,FlagSet.actual字段的偏移量是32;也就是说,bool类型的FlagSet.parsed字段实际占用8字节的存储空间.

bool类型的实际值只能是0或1,只需要占用一个字节即可,实际的机器指令也会读取一个字节. 也就是,flag.FlagSet类型的对象在存储时,因为8字节对齐,此处需要浪费7个字节的空间.

从以上字段数据可以看到,string类型的字段占16个字节,[]string类型的字段占24个字节,接口类型的字段占16个字节,与之前文章中分析得到的结果一直.

另外,可以看到map类型的字段,实际占用8个字节的空间,在之后的文章中将会详细介绍map类型.

仔细的读者可能已经注意到,flag.FlagSet类型没有任何方法,因为其uncommonType.mcount = 0.

在flag/flag.go源文件中,不是定义了很多方法吗?

以上代码清单中,flag.FlagSet类型的对象f为什么可以调用以下方法呢?

_ = f.Set("hello""world"
  f.PrintDefaults() 
  fmt.Println(f.Args()) 
  • 1.
  • 2.
  • 3.

实际上,flag/flag.go源文件中定义的方法的receiver都是*flag.FlagSet指针类型,没有flag.FlagSet类型.

// Args returns the non-flag arguments. 
func (f *FlagSet) Args() []string { return f.args } 
  • 1.
  • 2.

flag.FlagSet类型的对象f能够调用*flag.FlagSet指针类型的方法,只不过是编译器为方便开发者实现的语法糖而已.

在本例中,编译器会把flag.FlagSet类型的对象f的地址作为参数传递给*flag.FlagSet指针类型的方法.反之,编译器也是支持的.

指针类型*flag.FlagSet

为了方便查看类型信息,笔者开发了一个gdb的插件脚本.

查看*flag.FlagSet类型的信息如下,共包含38个方法,其中34个是公共方法.此处不再一一介绍.

(gdb) info type 0x4c68e0 
interfaceType { 
  rtype = { 
    size       = 0x8 (8) 
    ptrdata    = 0x8 (8) 
    hash       = 0xe05aa02c 
    tflag      = tflagUncommon | tflagRegularMemory 
    align      = 8 
    fieldAlign = 8 
    kind       = ptr 
    equal      = 0x403a00 <runtime.memequal64> 
    gcdata     = 0x4d2e28 
    str        = *flag.FlagSet 
    ptrToThis  = 0x0 (0x0) 
  } 
  elem  = 0x4c2ac0 -> flag.FlagSet 

uncommonType { 
  pkgpath = flag 
  mcount  = 38 
  xcount  = 34 
  moff    = 16 

methods [ 
  { 
    name = Arg 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Args 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Bool 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = BoolVar 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Duration 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = DurationVar 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = ErrorHandling 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Float64 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Float64Var 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Func 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Init 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Int 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Int64 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Int64Var 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = IntVar 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Lookup 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = NArg 
    mtyp = 0x4b0960 -> func() int 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = NFlag 
    mtyp = 0x4b0960 -> func() int 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Name 
    mtyp = 0x4b0b20 -> func() string 
    ifn  = 0x4a36e0 <flag.(*FlagSet).Name
    tfn  = 0x4a36e0 <flag.(*FlagSet).Name
  }, 
  { 
    name = Output 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Parse 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Parsed 
    mtyp = 0x4b0920 -> func() bool 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = PrintDefaults 
    mtyp = 0x4b0140 -> func() 
    ifn  = 0x4a3ec0 <flag.(*FlagSet).PrintDefaults> 
    tfn  = 0x4a3ec0 <flag.(*FlagSet).PrintDefaults> 
  }, 
  { 
    name = Set 
    mtyp = nil 
    ifn  = 0x4a37a0 <flag.(*FlagSet).Set
    tfn  = 0x4a37a0 <flag.(*FlagSet).Set
  }, 
  { 
    name = SetOutput 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = String 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = StringVar 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Uint 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Uint64 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Uint64Var 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = UintVar 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Var 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Visit 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = VisitAll 
    mtyp = nil 
    ifn  = 0x4a3700 <flag.(*FlagSet).VisitAll> 
    tfn  = 0x4a3700 <flag.(*FlagSet).VisitAll> 
  }, 
  { 
    name = defaultUsage 
    mtyp = 0x4b0140 -> func() 
    ifn  = 0x4a3f20 <flag.(*FlagSet).defaultUsage> 
    tfn  = 0x4a3f20 <flag.(*FlagSet).defaultUsage> 
  }, 
  { 
    name = failf 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = parseOne 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = usage 
    mtyp = 0x4b0140 -> func() 
    ifn  = nil 
    tfn  = nil 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.

结构体类型reflect.Value

实际上,编译器比想象的做的更多.

有时候,编译器会把源代码中的一个方法,编译出两个可执行的方法.在 内存中的接口类型 一文中,曾进行了详细分析.

直接运行gdb脚本查看reflect.Value类型信息,有3个字段,75个方法,此处为方便展示,省略了大部分方法信息.

(gdb) info type 0x4ca160 
structType { 
  rtype   = { 
    size       = 0x18 (24) 
    ptrdata    = 0x10 (16) 
    hash       = 0x500c1abc 
    tflag      = tflagUncommon | tflagExtraStar | tflagNamed | tflagRegularMemory 
    align      = 8 
    fieldAlign = 8 
    kind       = struct 
    equal      = 0x402720 <runtime.memequal_varlen> 
    gcdata     = 0x4d2e48 
    str        = reflect.Value 
    ptrToThis  = 0x23c60 (0x4c9c60) 
  } 
  pkgPath = reflect 
  fields  = [   
    { 
      name        = 0x4875094 -> typ 
      typ         = 0x4c6e60 -> *reflect.rtype 
      offsetEmbed = 0x0 (0)  
    },   
    { 
      name        = 0x4874896 -> ptr 
      typ         = 0x4b13e0 -> unsafe.Pointer 
      offsetEmbed = 0x8 (8)  
    },   
    { 
      name        = 0x4875112 -> flag 
      typ         = 0x4be7c0 -> reflect.flag 
      offsetEmbed = 0x10 (16) embed 
    } 
  ] 

uncommonType { 
  pkgpath = reflect 
  mcount  = 75 
  xcount  = 61 
  moff    = 88 

methods [ 
  { 
    name = Addr 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Bool 
    mtyp = 0x4b0920 -> func() bool 
    ifn  = nil 
    tfn  = 0x4881c0 <reflect.Value.Bool> 
  }, 
  ...... 
  { 
    name = Kind 
    mtyp = 0x4b0aa0 -> func() reflect.Kind 
    ifn  = 0x48d500 <reflect.(*Value).Kind> 
    tfn  = 0x489400 <reflect.Value.Kind> 
  }, 
  { 
    name = Len 
    mtyp = 0x4b0960 -> func() int 
    ifn  = 0x48d560 <reflect.(*Value).Len> 
    tfn  = 0x489420 <reflect.Value.Len> 
  }, 
  ...... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.

再看*reflect.Value指针类型的信息,没有任何字段(毕竟是指针),也有75个方法.

(gdb) info type 0x4c9c60 
interfaceType { 
  rtype = { 
    size       = 0x8 (8) 
    ptrdata    = 0x8 (8) 
    hash       = 0xf764ad0 
    tflag      = tflagUncommon | tflagRegularMemory 
    align      = 8 
    fieldAlign = 8 
    kind       = ptr 
    equal      = 0x403a00 <runtime.memequal64> 
    gcdata     = 0x4d2e28 
    str        = *reflect.Value 
    ptrToThis  = 0x0 (0x0) 
  } 
  elem  = 0x4ca160 -> reflect.Value 

uncommonType { 
  pkgpath = reflect 
  mcount  = 75 
  xcount  = 61 
  moff    = 16 

methods [ 
  { 
    name = Addr 
    mtyp = nil 
    ifn  = nil 
    tfn  = nil 
  }, 
  { 
    name = Bool 
    mtyp = 0x4b0920 -> func() bool 
    ifn  = nil 
    tfn  = nil 
  }, 
  ...... 
  { 
    name = Kind 
    mtyp = 0x4b0aa0 -> func() reflect.Kind 
    ifn  = 0x48d500 <reflect.(*Value).Kind> 
    tfn  = 0x48d500 <reflect.(*Value).Kind> 
  }, 
  { 
    name = Len 
    mtyp = 0x4b0960 -> func() int 
    ifn  = 0x48d560 <reflect.(*Value).Len> 
    tfn  = 0x48d560 <reflect.(*Value).Len> 
  }, 
  ...... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

我们可以清楚地看到,在源码中Len()方法,编译之后,生成了两个可执行方法,分别是:

  • reflect.Value.Len
  • reflect.(*Value).Len
func (v Value) Len() int { 
  k := v.kind() 
  switch k { 
  case Array: 
    tt := (*arrayType)(unsafe.Pointer(v.typ)) 
    return int(tt.len) 
  case Chan: 
    return chanlen(v.pointer()) 
  case Map: 
    return maplen(v.pointer()) 
  case Slice: 
    // Slice is bigger than a word; assume flagIndir. 
    return (*unsafeheader.Slice)(v.ptr).Len 
  case String: 
    // String is bigger than a word; assume flagIndir. 
    return (*unsafeheader.String)(v.ptr).Len 
  } 
  panic(&ValueError{"reflect.Value.Len", v.kind()}) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

通过reflect.Value类型的对象调用时,实际可能执行的两个方法中的任何一个.

通过*reflect.Value类型的指针对象调用时,也可能执行的两个方法中的任何一个.

这完全是由编译器决定的.

但是通过接口调用时,执行的一定是reflect.(*Value).Len这个方法的指令集合.

自定义结构体千变万化,但是结构体类型信息相对还是单一,容易理解.

 

责任编辑:姜华 来源: Golang In Memory
相关推荐

2022-08-09 09:10:43

Kubernetes容器

2023-11-28 09:31:55

MySQL算法

2023-11-21 08:03:43

语言架构偏移量

2023-03-27 17:58:34

MySQL加锁间隙锁

2023-03-30 08:52:40

DartFlutter

2021-06-02 05:43:36

比特币虚拟货币区块链

2022-08-03 08:01:16

CDN网站服务器

2023-09-18 08:02:45

CSS布局属性

2023-10-26 16:27:50

前端 WebCSS开发

2023-12-12 07:31:51

Executors工具开发者

2024-05-09 10:11:30

2022-09-01 08:01:56

Pythongunicorn

2023-04-04 08:01:47

2022-09-05 09:25:53

KubernetesService

2022-01-04 08:54:32

Redis数据库数据类型

2022-09-09 10:00:13

KubernetesConfigMap

2024-10-16 10:11:52

2019-09-27 08:53:47

Redis数据C语言

2020-01-14 12:08:32

内存安全

2024-02-23 19:11:13

C++编程开发
点赞
收藏

51CTO技术栈公众号