加入收藏 | 设为首页 | 会员中心 | 我要投稿 财气旺网 - 财气网 (https://www.caiqiwang.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

Go语言之再 论整数类型

发布时间:2022-11-26 12:39:21 所属栏目:语言 来源:
导读:   前言
  【Go】内存中的整数 一文详细介绍了int类型,对 int 数据及其类型建立起基本的认识。
  
  再谈整数类型的目的,是为了进一步剖析Go语言的类型系统,从底层化解潜在的错误认知。
  
  

   前言
  【Go】内存中的整数 一文详细介绍了int类型,对 int 数据及其类型建立起基本的认识。
  
  再谈整数类型的目的,是为了进一步剖析Go语言的类型系统,从底层化解潜在的错误认知。
  
  在Go语言中,type关键字不仅可以定义结构体(struct)和接口(interface),实际上可以用于声明任何数据类型,非常非常地强悍。例如,
  
  type calc func(a, b int) int
  
  type Foo int
  有人说,在以上代码中,type关键字的作用是定义类型的别名,Foo就是int的别名,Foo类型就是int类型。
  
  本文将带你深入了解int类型与Foo类型,保证你吃不了亏,保证你上不了当。
  
  环境
  OS : Ubuntu 20.04.2 LTS; x86_64  
  Go : go version go1.16.2 linux/amd64
  声明
  操作系统、处理器架构、Go版本不同,均有可能造成相同的源码编译后运行时的寄存器值、内存地址、数据结构等存在差异。
  
  本文仅包含 64 位系统架构下的 64 位可执行程序的研究分析。
  
  本文仅保证学习过程中的分析数据在当前环境下的准确有效性。
  
  代码清单
  int_kind.go
  
  package main
  
  import "fmt"
  import "reflect"
  import "strconv"
  
  type Foo int
  
  //go:noinline
  func (f Foo) Ree() int {
    return int(f)
  }
  
  //go:noinline
  func (f Foo) String() string {
    return strconv.Itoa(f.Ree())
  }
  
  //go:noinline
  func (f Foo) print() {
    fmt.Println("foo is " + f.String())
  }
  
  func main() {
    Typeof(123)
    Typeof(Foo(456))
  }
  
  //go:noinline
  func Typeof(i interface{}) {
    t := reflect.TypeOf(i)
    fmt.Println("值  ", i)
    fmt.Println("名称", t.Name())
    fmt.Println("类型", t.String())
    fmt.Println("方法")
    num := t.NumMethod()
    if num > 0 {
      for j := 0; j < num; j++ {
        fmt.Println("  ", t.Method(j).Name, t.Method(j).Type)
      }
    }
    fmt.Println()
  }
  代码清单中,Typeof函数用于显示数据对象的类型信息。
  
  运行结果
  
  
  仅仅从运行结果看,我们就知道Foo类型不是int类型,Foo不是int的别名。
  
  数据结构介绍
  在reflect/type.go源文件中,定义了两个数据结构uncommonType和method,用于存储和解析数据类型的方法信息。
  
  type uncommonType struct {
      pkgPath nameOff  // 包路径名称偏移量
      mcount  uint16   // 方法的数量
      xcount  uint16   // 公共导出方法的数量
      moff    uint32   // [mcount]method 相对本对象起始地址的偏移量
      _       uint32   // unused
  }
  reflect.uncommonType结构体用于描述一个数据类型的包名和方法信息。
  
  // 非接口类型的方法
  type method struct {
      name nameOff // 方法名称偏移量
      mtyp typeOff // 方法类型偏移量
      ifn  textOff // 通过接口调用时的地址偏移量;接口类型本文不介绍
      tfn  textOff // 直接类型调用时的地址偏移量
  }
  reflect.method结构体用于描述一个方法,它是一个压缩格式的结构,每个字段的值都是一个相对偏移量。
  
  type nameOff int32 // offset to a name
  type typeOff int32 // offset to an *rtype
  type textOff int32 // offset from top of text section
  nameOff 是相对程序 .rodata 节起始地址的偏移量。
  typeOff 是相对程序 .rodata 节起始地址的偏移量。
  textOff 是相对程序 .text 节起始地址的偏移量。
  
  
  关于 reflect.name结构体的介绍,请阅读 【Go】内存中的整数 。
  内存分析
  在Typeof函数入口处设置断点,首先查看 123 这个 int 对象的类型信息。
  
  int 类型
  在 【Go】内存中的整数 一文,介绍了int类型信息占用 48 个字节, 实际上int类型信息占用 64 个字节,只不过int类型并没有任何方法(method),所以前文忽略了uncommonType数据。
  
  int类型信息结构如下伪代码所示:
  
  type intType struct {
    rtype
    u uncommonType
  }
  其结构分布如下图所示:
  
  
  
  本文要更进一步分析数据的类型,所以需要将uncommonType数据拿出来对比。
  
  
  
  rtype.size = 8
  rtype.ptrdata = 0
  rtype.hash = 0xf75371fa
  rtype.tflag = 0xf = reflect.tflagUncommon | reflect.tflagExtraStar | reflect.tflagNamed | reflect.tflagRegularMemory
  rtype.align = 8
  rtype.fieldAlign = 8
  rtype.kind = 2 = reflect.Int
  rtype.equal = 0x4fbd98 -> runtime.memequal64
  rtype.str = 0x000003e3 -> *int字符串
  rtype.ptrToThis = 0x00007c00 -> *int类型
  uncommonType.pkgPath = 0
  uncommonType.mcount = 0 -> 没有方法
  uncommonType.xcount = 0
  uncommonType.moff = 0x10
  将int类型数据绘制成图表如下:
  
  
  
  此处不再对int类型信息进行详细介绍,仅说明 rtype.tflag字段;该字段包含reflect.tflagUncommon标记,表示类型信息中包含uncommonType数据。
  
  uncommonType.mcount = 0表示类型信息中不包含方法信息。
  
  Foo 类型
  Foo类型因为包含方法信息,要比int类型复杂许多,其类型信息结构如下伪代码所示:
  
  type FooType struct {
    rtype
    u uncommonType
    methods [u.mcount]method
  }
  结构分布如下图所示:
  
  
  
  以同样的方式查看Foo类型数据:
  
  
  
  rtype.size = 8
  rtype.ptrdata = 0
  rtype.hash = 0xec552021
  rtype.tflag = 0xf = reflect.tflagUncommon | reflect.tflagExtraStar | reflect.tflagNamed | reflect.tflagRegularMemory
  rtype.align = 8
  rtype.fieldAlign = 8
  rtype.kind = 2 = reflect.Int
  rtype.equal = 0x4fbd98 -> runtime.memequal64
  rtype.str = 0x00002128 -> *main.Foo字符串
  rtype.ptrToThis = 0x00014c00 -> *Foo类型
  uncommonType.pkgPath = 0x000003c4 -> main字符串
  uncommonType.mcount = 3 -> 方法数量
  uncommonType.xcount = 2 -> 公共导出方法数量
  uncommonType.moff = 0x10
  method[0].name = 0x000001e8
  method[0].mtyp = 0x0000be60
  method[0].ifn = 0x000c7740
  method[0].tfn = 0x000c6fe0
  method[1].name = 0x00001025
  method[1].mtyp = 0x0000c0e0
  method[1].ifn = 0x000c77c0
  method[1].tfn = 0x000c7000
  method[2].name = 0x00000da0
  method[2].mtyp = 0x0000b600
  method[2].ifn = 0xffffffff
  method[2].tfn = 0xffffffff
  将Foo类型数据绘制成图表如下:
  
  
  
  类型对比
  int和Foo两种类型属于同一种数据类别(reflect.Kind),都是reflect.Int。
  int和Foo两种类型比较函数相同,都是runtime.memequal64。
  int和Foo数据对象内存大小相同,都是8。
  int和Foo数据对象内存对齐相同,都是8。
  int和Foo两种类型名称不同。
  int和Foo两种类型哈希种子不同。
  int和Foo两种类型方法数量不同。
  int和Foo两种类型的指针类型不同。
  类型方法
  我们再回顾一下reflect.method结构体的各个字段:
  
  name字段描述的是方法名称偏移量。
  mtyp字段描述的是方法类型信息偏移量;关于函数类型介绍,敬请期待。
  ifn字段描述的是接口调用该方法时的指令内存地址偏移量;关于接口类型介绍,敬请期待。
  tfn字段描述的是直接调用该方法时的指令内存地址偏移量。
  Foo类型有3个方法,它们的类型信息保存在0x4dd8e0地址处;通过偏移量计算地址,查看方法的名称、地址、指令。
 

(编辑:财气旺网 - 财气网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!