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

反射是如何獲取結(jié)構(gòu)體成員信息的?

開(kāi)發(fā) 前端
結(jié)構(gòu)體是占用一塊連續(xù)的內(nèi)存,一個(gè)結(jié)構(gòu)體變量的大小是由結(jié)構(gòu)體中的字段決定的,結(jié)構(gòu)體變量的地址等于結(jié)構(gòu)體第一個(gè)字段的首地址。

[[412989]]

本文轉(zhuǎn)載自微信公眾號(hào)「Golang夢(mèng)工廠」,作者AsongGo。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang夢(mèng)工廠公眾號(hào)。

前言

哈嘍,大家好,我是asong,今天這篇文章的目的主要是解答一位讀者的疑問(wèn),涉及知識(shí)點(diǎn)是反射和結(jié)構(gòu)體內(nèi)存布局。我們先看一下讀者的問(wèn)題:

我們通過(guò)兩個(gè)問(wèn)題來(lái)解決他的疑惑:

  1. 結(jié)構(gòu)體在內(nèi)存中是如何存儲(chǔ)的
  2. 反射獲取結(jié)構(gòu)體成員信息的過(guò)程

結(jié)構(gòu)體是如何存儲(chǔ)的

結(jié)構(gòu)體是占用一塊連續(xù)的內(nèi)存,一個(gè)結(jié)構(gòu)體變量的大小是由結(jié)構(gòu)體中的字段決定的,結(jié)構(gòu)體變量的地址等于結(jié)構(gòu)體第一個(gè)字段的首地址。示例:

  1. type User struct { 
  2.  Name string 
  3.  Age uint64 
  4.  Gender bool // true:男 false: 女 
  5.  
  6. func main(){ 
  7.  u := User
  8.    Name"asong"
  9.    Age: 18, 
  10.    Gender: false
  11.   } 
  12.  fmt.Printf("%p\n",&u) 
  13.  fmt.Printf("%p\n",&u.Name
  14. // 運(yùn)行結(jié)果 
  15. 0xc00000c060 
  16. 0xc00000c060 

從運(yùn)行結(jié)果我們可以驗(yàn)證了結(jié)構(gòu)體變量u的存放地址就是字段Name的首地址。

結(jié)構(gòu)體的內(nèi)存布局其實(shí)就是分配一段連續(xù)的內(nèi)存,具體是在棧上分配還是堆上分配取決于編譯器的逃逸分析,結(jié)構(gòu)體在內(nèi)存分配時(shí)還要考慮到內(nèi)存對(duì)齊。

對(duì)齊的作用和原因:CPU訪問(wèn)內(nèi)存時(shí),并不是逐個(gè)字節(jié)訪問(wèn),而是以字長(zhǎng)(word size)單位訪問(wèn)。比如32位的CPU,字長(zhǎng)為4字節(jié),那么CPU訪問(wèn)內(nèi)存的單位也是4字節(jié)。這樣設(shè)計(jì)可以減少CPU訪問(wèn)內(nèi)存的次數(shù),加大CPU訪問(wèn)內(nèi)存的吞吐量。假設(shè)我們需要讀取8個(gè)字節(jié)的數(shù)據(jù),一次讀取4個(gè)字節(jié)那么就只需讀取2次就可以。內(nèi)存對(duì)齊對(duì)實(shí)現(xiàn)變量的原子性操作也是有好處的,每次內(nèi)存訪問(wèn)都是原子的,如果變量的大小不超過(guò)字長(zhǎng),那么內(nèi)存對(duì)齊后,對(duì)該變量的訪問(wèn)就是原子的,這個(gè)特性在并發(fā)場(chǎng)景下至關(guān)重要。

C語(yǔ)言的內(nèi)存對(duì)齊規(guī)則與Go語(yǔ)言一樣,所以C語(yǔ)言的對(duì)齊規(guī)則對(duì)Go同樣適用:

  • 對(duì)于結(jié)構(gòu)的各個(gè)成員,第一個(gè)成員位于偏移為0的位置,結(jié)構(gòu)體第一個(gè)成員的偏移量(offset)為0,以后每個(gè)成員相對(duì)于結(jié)構(gòu)體首地址的 offset 都是該成員大小與有效對(duì)齊值中較小那個(gè)的整數(shù)倍,如有需要編譯器會(huì)在成員之間加上填充字節(jié)。
  • 除了結(jié)構(gòu)成員需要對(duì)齊,結(jié)構(gòu)本身也需要對(duì)齊,結(jié)構(gòu)的長(zhǎng)度必須是編譯器默認(rèn)的對(duì)齊長(zhǎng)度和成員中最長(zhǎng)類(lèi)型中最小的數(shù)據(jù)大小的倍數(shù)對(duì)齊。

根據(jù)這個(gè)規(guī)則我們來(lái)分析一下上面示例的結(jié)構(gòu)體User,這里我使用的mac,所以是64位CPU,編譯器默認(rèn)對(duì)齊參數(shù)是8,String、uint64、bool的對(duì)齊值分別是8、8、1,根據(jù)第一條規(guī)則分析:

  • 第一個(gè)字段類(lèi)型是string,對(duì)齊值是8,大小為16,所以放在內(nèi)存布局中的第一位。
  • 第二個(gè)字段類(lèi)型是uin64,對(duì)齊值是8,大小為8,所以他的內(nèi)存偏移值必須是8的倍數(shù),因?yàn)榈谝粋€(gè)字段Name占有16位,所以直接從16開(kāi)始不要補(bǔ)位。
  • 第三個(gè)字段類(lèi)型是bool,對(duì)齊值是1,大小為1,所以他的內(nèi)存偏移值必須是1的倍數(shù),因?yàn)閁ser的前兩個(gè)字段已經(jīng)排到了24位,所以下一個(gè)偏移量正好是24。

接下來(lái)我們?cè)诜治龅诙€(gè)規(guī)則:

  • 根據(jù)第一條內(nèi)存對(duì)齊規(guī)則分析后,內(nèi)存長(zhǎng)度已經(jīng)為25字節(jié)了,我們開(kāi)始使用第2條規(guī)則進(jìn)行對(duì)齊,默認(rèn)對(duì)齊值是8,字段中最大類(lèi)型的長(zhǎng)度是16,所以可以得出該結(jié)構(gòu)體的對(duì)齊值是8,我們目前的內(nèi)存長(zhǎng)度是25,不是8的倍數(shù),所以需要補(bǔ)全,所以最終的結(jié)果是32,補(bǔ)了7位,由編譯器進(jìn)行填充,一般為0值,也稱(chēng)之為空洞。

注意:這里對(duì)內(nèi)存對(duì)齊沒(méi)有說(shuō)的很細(xì),想要更深了解內(nèi)存對(duì)齊可以看我之前的一篇文章:Go看源碼必會(huì)知識(shí)之unsafe包

Go語(yǔ)言反射獲取結(jié)構(gòu)體成員信息

Go語(yǔ)言提供了一種機(jī)制在運(yùn)行時(shí)更新和檢查變量的值、調(diào)用變量的方法和變量的內(nèi)在操作,但是在編譯時(shí)并不知道這些變量的具體類(lèi)型,這種機(jī)制被稱(chēng)為反射。Go語(yǔ)言提供了 reflect 包來(lái)訪問(wèn)程序的反射信息。

我們可以通過(guò)調(diào)用reflect.TypeOf()獲得反射對(duì)象信息,如果他的類(lèi)型是結(jié)構(gòu)體,接著可以通過(guò)反射值對(duì)象reflect.Type的NumField和Field方法獲取結(jié)構(gòu)體成員的詳細(xì)信息,先看一個(gè)例子:

  1. type User struct { 
  2.  Name string 
  3.  Age uint64 
  4.  Gender bool // true:男 false: 女 
  5.  
  6.  
  7. func main()  { 
  8.  u := User
  9.   Name"asong"
  10.   Age: 18, 
  11.   Gender: false
  12.  } 
  13.  getType := reflect.TypeOf(u) 
  14.  for i:=0; i < getType.NumField(); i++{ 
  15.   fieldType := getType.Field(i) 
  16.   // 輸出成員名 
  17.   fmt.Printf("name: %v \n", fieldType.Name
  18.  } 
  19. // 運(yùn)行結(jié)果 
  20. nameName  
  21. name: Age  
  22. name: Gender  

接下來(lái)我們就一起來(lái)看一看Go語(yǔ)言是如何通過(guò)反射來(lái)獲取結(jié)構(gòu)體成員信息的。

首先我們來(lái)看一看reflect.TypeOf()方法是如何獲取到類(lèi)型的:

  1. func TypeOf(i interface{}) Type { 
  2.  eface := *(*emptyInterface)(unsafe.Pointer(&i)) 
  3.  return toType(eface.typ) 

我們知道在Go語(yǔ)言中任何類(lèi)型都可以轉(zhuǎn)成interface{}類(lèi)型,當(dāng)向接口變量賦于一個(gè)實(shí)體類(lèi)型的時(shí)候,接口會(huì)存儲(chǔ)實(shí)體的類(lèi)型信息,反射就是通過(guò)接口的類(lèi)型信息實(shí)現(xiàn)的。

一個(gè)空接口結(jié)構(gòu)如下:

  1. type eface struct { 
  2.     _type *_type 
  3.     data  unsafe.Pointer 

_type 字段,表示空接口所承載的具體的實(shí)體類(lèi)型。data 描述了具體的值,Go 語(yǔ)言里所有的類(lèi)型都 實(shí)現(xiàn)了 空接口。

所以在TypeOf方法中,我們就是通過(guò)讀取_type字段獲取到類(lèi)型。

現(xiàn)在我們已經(jīng)知道他是怎么獲取到具體的類(lèi)型了,接下來(lái)我們就來(lái)看一看NumField()方法是怎么獲取到字段的。

  1. func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) } 
  2. func (t *rtype) NumField() int { 
  3.  if t.Kind() != Struct { 
  4.   panic("reflect: NumField of non-struct type " + t.String()) 
  5.  } 
  6.  tt := (*structType)(unsafe.Pointer(t)) 
  7.  return len(tt.fields) 

因?yàn)橹挥衧truct類(lèi)型才可以調(diào)用,所以在NumFiled()方法中做了類(lèi)型檢查,如果不是struct類(lèi)型則直接發(fā)生panic,然后會(huì)rtype類(lèi)型強(qiáng)制轉(zhuǎn)換成structType,最后返回結(jié)構(gòu)體成員字段的數(shù)量。

  1. // structType represents a struct type. 
  2. type structType struct { 
  3.  rtype 
  4.  pkgPath name 
  5.  fields  []structField // sorted by offset 
  6. // Struct field 
  7. type structField struct { 
  8.  name        name    // name is always non-empty 
  9.  typ         *rtype  // type of field 
  10.  offsetEmbed uintptr // byte offset of field<<1 | isEmbedded 

調(diào)用Field()方法會(huì)根據(jù)索引返回對(duì)應(yīng)的結(jié)構(gòu)體字段的信息,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生panic。

  1. func (t *rtype) Field(i int) StructField { 
  2.   // 類(lèi)型檢查 
  3.  if t.Kind() != Struct { 
  4.   panic("reflect: Field of non-struct type " + t.String()) 
  5.  } 
  6.   // 強(qiáng)制轉(zhuǎn)換成structType 類(lèi)型 
  7.  tt := (*structType)(unsafe.Pointer(t)) 
  8.  return tt.Field(i) 
  9. // Field returns the i'th struct field. 
  10. func (t *structType) Field(i int) (f StructField) { 
  11.   // 溢出檢查 
  12.  if i < 0 || i >= len(t.fields) { 
  13.   panic("reflect: Field index out of bounds"
  14.  } 
  15.  // 獲取之前structType中fields字段的值 
  16.  p := &t.fields[i] 
  17.   // 轉(zhuǎn)換成StructFiled結(jié)構(gòu)體 
  18.  f.Type = toType(p.typ) 
  19.  f.Name = p.name.name() 
  20.   // 判斷是否是匿名結(jié)構(gòu)體 
  21.  f.Anonymous = p.embedded() 
  22.  if !p.name.isExported() { 
  23.   f.PkgPath = t.pkgPath.name() 
  24.  } 
  25.  if tag := p.name.tag(); tag != "" { 
  26.   f.Tag = StructTag(tag) 
  27.  } 
  28.   // 獲取字段的偏移量 
  29.  f.Offset = p.offset() 
  30.   // 獲取索引值 
  31.  f.Index = []int{i} 
  32.  return 

返回StructField結(jié)構(gòu)如下:

  1. // A StructField describes a single field in a struct. 
  2. type StructField struct { 
  3.    Name string // 字段名 
  4.    PkgPath string // 字段路徑 
  5.    Type      Type      // 字段反射類(lèi)型對(duì)象 
  6.    Tag       StructTag // 字段的結(jié)構(gòu)體標(biāo)簽 
  7.    Offset    uintptr   // 字段在結(jié)構(gòu)體中的相對(duì)偏移 
  8.    Index     []int     // Type.FieldByIndex中的返回的索引值 
  9.    Anonymous bool      // 是否為匿名字段 

到這里整個(gè)反射獲取結(jié)構(gòu)體成員信息的過(guò)程應(yīng)該很明朗了吧~。

**小結(jié):**因?yàn)镚o 語(yǔ)言里所有的類(lèi)型都 實(shí)現(xiàn)了 空接口,所以可以根據(jù)這個(gè)特性獲取到數(shù)據(jù)類(lèi)型以及存放數(shù)據(jù)的地址,對(duì)于結(jié)構(gòu)體類(lèi)型,將其轉(zhuǎn)換為structType類(lèi)型,最后轉(zhuǎn)換成StructField結(jié)構(gòu)獲取所有結(jié)構(gòu)體信息。

總結(jié)

 

本文沒(méi)想詳細(xì)展開(kāi)講解Go語(yǔ)言反射的原理和過(guò)程,只是簡(jiǎn)單介紹了一下反射獲取到結(jié)構(gòu)體成員信息的過(guò)程,更多關(guān)于反射知識(shí)的講解會(huì)在后面持續(xù)更新,敬請(qǐng)期待~。

 

責(zé)任編輯:武曉燕 來(lái)源: Golang夢(mèng)工廠
相關(guān)推薦

2014-04-01 10:11:33

C語(yǔ)言指針

2022-09-30 15:03:09

C語(yǔ)言深拷貝淺拷貝

2020-12-20 09:59:13

Go語(yǔ)言基礎(chǔ)技術(shù)

2021-05-11 11:31:52

C語(yǔ)言類(lèi)型指針

2020-01-13 14:39:06

FlinkSQL無(wú)限流

2021-11-26 12:00:07

包裝器SwiftU結(jié)構(gòu)體

2023-07-29 15:03:29

2009-08-13 11:18:50

C#結(jié)構(gòu)體

2009-08-13 14:46:03

C#結(jié)構(gòu)體定義

2021-04-20 09:00:48

Go 語(yǔ)言結(jié)構(gòu)體type

2009-08-14 11:05:28

C#語(yǔ)言的結(jié)構(gòu)體

2020-12-02 09:10:22

Go結(jié)構(gòu)數(shù)據(jù)類(lèi)型

2021-10-26 00:19:51

C++結(jié)構(gòu)體存儲(chǔ)

2024-09-29 09:02:17

Go語(yǔ)言類(lèi)型

2020-07-21 15:20:20

語(yǔ)言結(jié)構(gòu)體共用體

2023-06-12 00:20:42

Go配置管理庫(kù)

2021-07-14 08:31:08

Java反射機(jī)制Class類(lèi)

2009-08-13 14:24:44

C#結(jié)構(gòu)體構(gòu)造函數(shù)

2014-02-10 15:05:37

C語(yǔ)言封裝

2011-04-11 13:00:08

C++結(jié)構(gòu)體枚舉
點(diǎn)贊
收藏

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