自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go語言之再談?wù)麛?shù)類型

開發(fā) 后端
在Go語言中,type關(guān)鍵字不僅可以定義結(jié)構(gòu)體(struct)和接口(interface),實際上可以用于聲明任何數(shù)據(jù)類型,非常非常地強悍。

[[427102]]

 前言

【Go】內(nèi)存中的整數(shù) 一文詳細介紹了int類型,對 int 數(shù)據(jù)及其類型建立起基本的認識。

再談?wù)麛?shù)類型的目的,是為了進一步剖析Go語言的類型系統(tǒng),從底層化解潛在的錯誤認知。

在Go語言中,type關(guān)鍵字不僅可以定義結(jié)構(gòu)體(struct)和接口(interface),實際上可以用于聲明任何數(shù)據(jù)類型,非常非常地強悍。例如,

  1. type calc func(a, b intint 
  2.  
  3. type Foo int 

有人說,在以上代碼中,type關(guān)鍵字的作用是定義類型的別名,F(xiàn)oo就是int的別名,F(xiàn)oo類型就是int類型。

本文將帶你深入了解int類型與Foo類型,保證你吃不了虧,保證你上不了當(dāng)。

環(huán)境

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

聲明

操作系統(tǒng)、處理器架構(gòu)、Go版本不同,均有可能造成相同的源碼編譯后運行時的寄存器值、內(nèi)存地址、數(shù)據(jù)結(jié)構(gòu)等存在差異。

本文僅包含 64 位系統(tǒng)架構(gòu)下的 64 位可執(zhí)行程序的研究分析。

本文僅保證學(xué)習(xí)過程中的分析數(shù)據(jù)在當(dāng)前環(huán)境下的準(zhǔn)確有效性。

代碼清單

int_kind.go

  1. package main 
  2.  
  3. import "fmt" 
  4. import "reflect" 
  5. import "strconv" 
  6.  
  7. type Foo int 
  8.  
  9. //go:noinline 
  10. func (f Foo) Ree() int { 
  11.   return int(f) 
  12.  
  13. //go:noinline 
  14. func (f Foo) String() string { 
  15.   return strconv.Itoa(f.Ree()) 
  16.  
  17. //go:noinline 
  18. func (f Foo) print() { 
  19.   fmt.Println("foo is " + f.String()) 
  20.  
  21. func main() { 
  22.   Typeof(123) 
  23.   Typeof(Foo(456)) 
  24.  
  25. //go:noinline 
  26. func Typeof(i interface{}) { 
  27.   t := reflect.TypeOf(i) 
  28.   fmt.Println("值  ", i) 
  29.   fmt.Println("名稱", t.Name()) 
  30.   fmt.Println("類型", t.String()) 
  31.   fmt.Println("方法"
  32.   num := t.NumMethod() 
  33.   if num > 0 { 
  34.     for j := 0; j < num; j++ { 
  35.       fmt.Println("  ", t.Method(j).Name, t.Method(j).Type) 
  36.     } 
  37.   } 
  38.   fmt.Println() 

代碼清單中,Typeof函數(shù)用于顯示數(shù)據(jù)對象的類型信息。

運行結(jié)果

僅僅從運行結(jié)果看,我們就知道Foo類型不是int類型,F(xiàn)oo不是int的別名。

數(shù)據(jù)結(jié)構(gòu)介紹

在reflect/type.go源文件中,定義了兩個數(shù)據(jù)結(jié)構(gòu)uncommonType和method,用于存儲和解析數(shù)據(jù)類型的方法信息。

  1. type uncommonType struct { 
  2.     pkgPath nameOff  // 包路徑名稱偏移量 
  3.     mcount  uint16   // 方法的數(shù)量 
  4.     xcount  uint16   // 公共導(dǎo)出方法的數(shù)量 
  5.     moff    uint32   // [mcount]method 相對本對象起始地址的偏移量 
  6.     _       uint32   // unused 

reflect.uncommonType結(jié)構(gòu)體用于描述一個數(shù)據(jù)類型的包名和方法信息。

  1. // 非接口類型的方法 
  2. type method struct { 
  3.     name nameOff // 方法名稱偏移量 
  4.     mtyp typeOff // 方法類型偏移量 
  5.     ifn  textOff // 通過接口調(diào)用時的地址偏移量;接口類型本文不介紹 
  6.     tfn  textOff // 直接類型調(diào)用時的地址偏移量 

reflect.method結(jié)構(gòu)體用于描述一個方法,它是一個壓縮格式的結(jié)構(gòu),每個字段的值都是一個相對偏移量。

  1. type nameOff int32 // offset to a name 
  2. type typeOff int32 // offset to an *rtype 
  3. type textOff int32 // offset from top of text section 
  • nameOff 是相對程序 .rodata 節(jié)起始地址的偏移量。
  • typeOff 是相對程序 .rodata 節(jié)起始地址的偏移量。
  • textOff 是相對程序 .text 節(jié)起始地址的偏移量。

  • 關(guān)于 reflect.name結(jié)構(gòu)體的介紹,請閱讀 【Go】內(nèi)存中的整數(shù) 。

內(nèi)存分析

在Typeof函數(shù)入口處設(shè)置斷點,首先查看 123 這個 int 對象的類型信息。

int 類型

在 【Go】內(nèi)存中的整數(shù) 一文,介紹了int類型信息占用 48 個字節(jié), 實際上int類型信息占用 64 個字節(jié),只不過int類型并沒有任何方法(method),所以前文忽略了uncommonType數(shù)據(jù)。

int類型信息結(jié)構(gòu)如下偽代碼所示:

  1. type intType struct { 
  2.   rtype 
  3.   u uncommonType 

其結(jié)構(gòu)分布如下圖所示:

本文要更進一步分析數(shù)據(jù)的類型,所以需要將uncommonType數(shù)據(jù)拿出來對比。

  • 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類型數(shù)據(jù)繪制成圖表如下:

此處不再對int類型信息進行詳細介紹,僅說明 rtype.tflag字段;該字段包含reflect.tflagUncommon標(biāo)記,表示類型信息中包含uncommonType數(shù)據(jù)。

uncommonType.mcount = 0表示類型信息中不包含方法信息。

Foo 類型

Foo類型因為包含方法信息,要比int類型復(fù)雜許多,其類型信息結(jié)構(gòu)如下偽代碼所示:

  1. type FooType struct { 
  2.   rtype 
  3.   u uncommonType 
  4.   methods [u.mcount]method 

結(jié)構(gòu)分布如下圖所示:

以同樣的方式查看Foo類型數(shù)據(jù):

  • 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 -> 方法數(shù)量
  • uncommonType.xcount = 2 -> 公共導(dǎo)出方法數(shù)量
  • 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類型數(shù)據(jù)繪制成圖表如下:

類型對比

  • int和Foo兩種類型屬于同一種數(shù)據(jù)類別(reflect.Kind),都是reflect.Int。
  • int和Foo兩種類型比較函數(shù)相同,都是runtime.memequal64。
  • int和Foo數(shù)據(jù)對象內(nèi)存大小相同,都是8。
  • int和Foo數(shù)據(jù)對象內(nèi)存對齊相同,都是8。
  • int和Foo兩種類型名稱不同。
  • int和Foo兩種類型哈希種子不同。
  • int和Foo兩種類型方法數(shù)量不同。
  • int和Foo兩種類型的指針類型不同。

類型方法

我們再回顧一下reflect.method結(jié)構(gòu)體的各個字段:

  • name字段描述的是方法名稱偏移量。
  • mtyp字段描述的是方法類型信息偏移量;關(guān)于函數(shù)類型介紹,敬請期待。
  • ifn字段描述的是接口調(diào)用該方法時的指令內(nèi)存地址偏移量;關(guān)于接口類型介紹,敬請期待。
  • tfn字段描述的是直接調(diào)用該方法時的指令內(nèi)存地址偏移量。

Foo類型有3個方法,它們的類型信息保存在0x4dd8e0地址處;通過偏移量計算地址,查看方法的名稱、地址、指令。

方法名稱

  • methods[0].name = Ree
  • methods[1].name = String
  • methods[2].name = print

從內(nèi)存分析數(shù)據(jù)看,F(xiàn)oo類型的三個方法信息的保存順序似乎與源碼中定義的順序相同,其實不然。

數(shù)據(jù)類型的方法信息保存順序是大寫字母開頭的公共導(dǎo)出方法在前,小寫字母開頭的包私有方法在后,我們可以通過reflect/type.go源文件中的代碼印證這一點:

  1. func (t *uncommonType) methods() []method { 
  2.   if t.mcount == 0 { 
  3.     return nil 
  4.   } 
  5.   return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.mcount > 0"))[:t.mcount:t.mcount] 
  6.  
  7. func (t *uncommonType) exportedMethods() []method { 
  8.   if t.xcount == 0 { 
  9.     return nil 
  10.   } 
  11.   return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount] 

方法類型

關(guān)于函數(shù)類型與接口方法,后續(xù)會有專題文章詳細介紹,本文將不再深入探究。

方法地址

從內(nèi)存數(shù)據(jù)看到,

  • Ree方法的地址偏移是0x000c6fe0,通過計算可以在0x4c7fe0地址處找到其機器指令。
  • String方法的地址偏移是0x000c7000,通過計算可以在0x4c8000地址處找到其機器指令。
  • print方法的地址偏移是0xffffffff,也就是-1,意思是找不到該方法。

我們明明在源碼中定義了print方法,為什么找不到該方法呢?

原因是:print方法是一個私有方法,不會被外部調(diào)用,但是main包范圍內(nèi)又沒有調(diào)用者; Go編譯器本著勤儉節(jié)約的原則,把print方法優(yōu)化丟棄掉了,即使使用go:noinline指令禁止內(nèi)斂也不管用,就是直接干掉。

Go編譯器的類似優(yōu)化行為隨處可見,在后續(xù)文章中會逐步介紹。

通過本文,詳細你對 type 關(guān)鍵字有了更加深入的了解,對 Go 語言的類型系統(tǒng)有了更加深入的了解,和想象中的是否有所不同?

責(zé)任編輯:武曉燕 來源: Golang In Memory
相關(guān)推薦

2021-10-23 06:42:14

Go語言接口

2024-05-10 08:04:44

開發(fā)者Go語言

2020-12-31 09:06:44

Go語言Reflect

2021-10-16 17:53:35

Go函數(shù)編程

2021-10-09 07:52:01

Go程序重命名

2022-03-28 13:34:26

Go泛型部署泛型

2019-01-03 09:45:20

Go 前端 Web

2013-08-20 10:11:20

Go系統(tǒng)管理員

2024-01-05 20:46:14

2021-10-18 10:53:26

Go 代碼技術(shù)

2021-05-12 08:53:54

Go語言調(diào)度

2023-06-26 00:03:55

Go語言類型

2023-07-16 23:43:05

Go語言模式

2024-01-08 08:23:07

Go語言代碼

2013-07-10 11:11:05

PythonGo語言

2012-08-13 14:13:46

2024-04-26 00:01:00

Go語言類型

2017-06-14 09:37:05

R語言Apriori算法

2018-08-01 15:10:02

GolangPython語言

2012-02-13 10:03:31

編程開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號