当前位置: 首页 > 科技观察

重温Go语言中的整数类型

时间:2023-03-21 21:12:17 科技观察

前言【Go】内存中的整数本文详细介绍了int类型,建立了对int数据及其类型的基本认识。讲整数类型的目的是进一步分析Go语言的类型体系,从底层解决潜在的误解。在Go语言中,type关键字不仅可以定义结构体(struct)和接口(interface),实际上可以用来声明任何数据类型,非常非常强大。例如,typecalcfunc(a,bint)inttypeFooint有人说,在上面的代码中,type关键字的作用是定义类型的别名,Foo是int的别名,Foo类型就是int类型。本文将带你深入了解int类型和Foo类型,保证你不吃亏,不上当。环境操作系统:Ubuntu20.04.2LTS;x86_64Go:goversiongo1.16.2linux/amd64声明操作系统、处理器架构、Go版本不同,可能导致编译相同源码时存在寄存器值、内存地址、数据结构。不同之处。本文仅包括64位系统架构下64位可执行程序的研究和分析。本文仅保证当前环境下学习过程中分析数据的准确性和有效性。代码清单int_kind.gopackagemainimport"fmt"import"reflect"import"strconv"typeFooint//go:noinlinefunc(fFoo)Ree()int{returnint(f)}//go:noinlinefunc(fFoo)String()string{returnstrconv.Itoa(f.Ree())}//go:noinlinefunc(fFoo)print(){fmt.Println("foois"+f.String())}funcmain(){Typeof(123)Typeof(Foo(456))}//go:noinlinefuncTypeof(iinterface{}){t:=reflect.TypeOf(i)fmt.Println("value",i)fmt.Println("name",t.Name())fmt.Println("类型",t.String())fmt.Println("方法")num:=t.NumMethod()ifnum>0{forj:=0;jruntime.memequal64rtype.str=0x000003e3->*int字符串rtype.ptrToThis=0x00007c00->*inttypeuncommonType.pkgPath=0uncommonType.mcount=0->nomethoduncommonType.xcount=0uncommonType.moff=0x10int类型数据绘制成图表如下:这里int类型信息不再详细介绍,只解释了rtype.tflag字段;该字段包含reflect.tflagUncommon标志,表示类型信息为ContainsuncommonTypedata。uncommonType.mcount=0表示方法信息不包含在类型信息中。Foo类型Foo类型比int类型复杂得多,因为它包含方法信息。其类型信息结构如下伪代码所示:typeFooTypestruct{rtypeuuncommonTypemethods[u.mcount]method}结构分布如下图所示:同理查看Foo类型Data:rtype.size=8rtype.ptrdata=0rtype.hash=0xec552021rtype.tflag=0xf=reflect.tflagUncommon|反射.tflagExtraStar|反射.tflagNamed|reflect.tflagRegularMemoryrtype.align=8rtype.fieldAlign=8rtype。kind=2=reflect.Intrtype.equal=0x4fbd98->runtime.memequal64rtype.str=0x00002128->*main.Foostringrtype.ptrToThis=0x00014c00->*FootypeuncommonType.pkgPath=0x000003c4->mainstringuncommonType。mcount=3->方法数量uncommonType.xcount=2->常用导出方法数量uncommonType.moff=0x10method[0].name=0x000001e8method[0].mtyp=0x0000be60method[0].ifn=0x000c7740method[0].tfn=0x000c6fe0方法[1].name=0x00001025方法[1].mtyp=0x0000c0e0方法[1].ifn=0x000c77c0方法[1].tfn=0x000c7000方法[2].name=0x00000da0method[2].mtyp=0x0000b600method[2].ifn=0xffffffffmethod[2].tfn=0xffffffffFoo类型数据绘制成图形如下:类型比较int和Foo属于同一数据类别(体现.kind),reflect.Intint和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;地址由偏移量计算,查看方法名、地址、指令。方法名methods[0].name=Reemethods[1].name=Stringmethods[2].name=print从内存分析资料来看,Foo类型的三个方法信息的存储顺序好像是一样的源代码中定义的顺序,实际上不是。该数据类型的方法信息的存储顺序为大写字母开头的公共导出方法在前,小写字母开头的包私有方法在后。我们可以通过reflect/type.go源文件中的代码确认这一点:func(t*uncommonType)methods()[]method{ift.mcount==0{returnnil}return(*[1<<16]method)(add(unsafe.Pointer(t),uintptr(t.moff),"t.mcount>0"))[:t.mcount:t.mcount]}func(t*uncommonType)exportedMethods()[]method{ift.xcount==0{returnnil}return(*[1<<16]method)(add(unsafe.Pointer(t),uintptr(t.moff),"t.xcount>0"))[:t.xcount:t.xcount]}方法类型关于函数类型和接口方法,后面会有专门的文章详细介绍,本文不再深究。方法地址从内存数据来看,Ree方法的地址偏移量为0x000c6fe0,通过计算可以在地址0x4c7fe0处找到其机器指令。String方法的地址偏移量是0x000c7000,通过计算可以在地址0x4c8000找到它的机器指令。打印方法的地址偏移量为0xffffffff,即-1,表示找不到该方法。我们在源码中明明定义了print方法,为什么找不到这个方法呢?原因是:print方法是私有方法,不会被外部调用,但是在main包的范围内没有调用者;Go编译器基于thrift经济的原则,舍弃了print方法的优化。即使用go:noinline命令禁止内向,也不行,直接kill掉。Go编译器类似的优化行为比比皆是,后续文章会逐步介绍。通过这篇文章,详细的,你对Go语言的type关键字和类型系统有了更深入的了解。是不是和想象中的不一样?