【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
在Typeof函数入口处设置断点,首先查看 123 这个 int 对象的类型信息。
在 【Go】内存中的整数 一文,介绍了int类型信息占用 48 个字节, 实际上int类型信息占用 64 个字节,只不过int类型并没有任何方法(method),所以前文忽略了uncommonType数据。
int类型信息结构如下伪代码所示:
- type intType struct {
- rtype
- u uncommonType
- }
其结构分布如下图所示:
本文要更进一步分析数据的类型,所以需要将uncommonType数据拿出来对比。
将int类型数据绘制成图表如下:
此处不再对int类型信息进行详细介绍,仅说明 rtype.tflag字段;该字段包含reflect.tflagUncommon标记,表示类型信息中包含uncommonType数据。
uncommonType.mcount = 0表示类型信息中不包含方法信息。
Foo类型因为包含方法信息,要比int类型复杂许多,其类型信息结构如下伪代码所示:
- type FooType struct {
- rtype
- u uncommonType
- methods [u.mcount]method
- }
结构分布如下图所示:
以同样的方式查看Foo类型数据:
将Foo类型数据绘制成图表如下:
我们再回顾一下reflect.method结构体的各个字段:
Foo类型有3个方法,它们的类型信息保存在0x4dd8e0地址处;通过偏移量计算地址,查看方法的名称、地址、指令。
从内存分析数据看,Foo类型的三个方法信息的保存顺序似乎与源码中定义的顺序相同,其实不然。
数据类型的方法信息保存顺序是大写字母开头的公共导出方法在前,小写字母开头的包私有方法在后,我们可以通过reflect/type.go源文件中的代码印证这一点:
- func (t *uncommonType) methods() []method {
- if t.mcount == 0 {
- return nil
- }
- return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.mcount > 0"))[:t.mcount:t.mcount]
- }
- func (t *uncommonType) exportedMethods() []method {
- if t.xcount == 0 {
- return nil
- }
- return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
- }
关于函数类型与接口方法,后续会有专题文章详细介绍,本文将不再深入探究。
从内存数据看到,
我们明明在源码中定义了print方法,为什么找不到该方法呢?
原因是:print方法是一个私有方法,不会被外部调用,但是main包范围内又没有调用者; Go编译器本着勤俭节约的原则,把print方法优化丢弃掉了,即使使用go:noinline指令禁止内敛也不管用,就是直接干掉。
Go编译器的类似优化行为随处可见,在后续文章中会逐步介绍。
通过本文,详细你对 type 关键字有了更加深入的了解,对 Go 语言的类型系统有了更加深入的了解,和想象中的是否有所不同?
当前名称:Go语言之再谈整数类型
文章链接:http://www.shufengxianlan.com/qtweb/news44/65994.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联