原文链接:读者发问:反射是如何获取构造体成员信息的?

前言

asong反射

咱们通过两个问题来解决他的纳闷:

  1. 构造体在内存中是如何存储的
  2. 反射获取构造体成员信息的过程

构造体是如何存储的

构造体是占用一块间断的内存,一个构造体变量的大小是由构造体中的字段决定的,构造体变量的地址等于构造体第一个字段的首地址。示例:

type User struct {
    Name string
    Age uint64
    Gender bool // true:男 false: 女
}

func main(){
    u := User{
            Name: "asong",
            Age: 18,
            Gender: false,
        }
    fmt.Printf("%p\n",&u)
    fmt.Printf("%p\n",&u.Name)
}
// 运行后果
0xc00000c060
0xc00000c060
uName

构造体的内存布局其实就是调配一段间断的内存,具体是在栈上调配还是堆上调配取决于编译器的逃逸剖析,构造体在内存调配时还要思考到内存对齐。

CPUword size32CPU4CPU4CPUCPU42
CGoCGo
  • 对于构造的各个成员,第一个成员位于偏移为0的地位,构造体第一个成员的偏移量(offset)为0,当前每个成员绝对于构造体首地址的 offset 都是该成员大小与无效对齐值中较小那个的整数倍,如有须要编译器会在成员之间加上填充字节。
  • 除了构造成员须要对齐,构造自身也须要对齐,构造的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。
Usermac64CPU8Stringuint64bool881
string816uin64888Name16bool111User2424

接下来咱们在剖析第二个规定:

25281682583270

留神:这里对内存对齐没有说的很细,想要更深理解内存对齐能够看我之前的一篇文章:Go看源码必会常识之unsafe包

Go语言反射获取构造体成员信息

Go语言提供了一种机制在运行时更新和查看变量的值、调用变量的办法和变量的外在操作,然而在编译时并不知道这些变量的具体类型,这种机制被称为反射。Go语言提供了 reflect 包来拜访程序的反射信息。

reflect.TypeOf()reflect.TypeNumFieldField
type User struct {
    Name string
    Age uint64
    Gender bool // true:男 false: 女
}


func main()  {
    u := User{
        Name: "asong",
        Age: 18,
        Gender: false,
    }
    getType := reflect.TypeOf(u)
    for i:=0; i < getType.NumField(); i++{
        fieldType := getType.Field(i)
        // 输入成员名
        fmt.Printf("name: %v \n", fieldType.Name)
    }
}
// 运行后果
name: Name 
name: Age 
name: Gender 
Go
reflect.TypeOf()
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}
Gointerface{}

一个空接口构造如下:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
_typedata实现了
TypeOf_type
NumField()
func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }
func (t *rtype) NumField() int {
    if t.Kind() != Struct {
        panic("reflect: NumField of non-struct type " + t.String())
    }
    tt := (*structType)(unsafe.Pointer(t))
    return len(tt.fields)
}
structNumFiled()structpanicrtypestructType
// structType represents a struct type.
type structType struct {
    rtype
    pkgPath name
    fields  []structField // sorted by offset
}
// Struct field
type structField struct {
    name        name    // name is always non-empty
    typ         *rtype  // type of field
    offsetEmbed uintptr // byte offset of field<<1 | isEmbedded
}
Field()panic
func (t *rtype) Field(i int) StructField {
  // 类型查看
    if t.Kind() != Struct {
        panic("reflect: Field of non-struct type " + t.String())
    }
  // 强制转换成structType 类型
    tt := (*structType)(unsafe.Pointer(t))
    return tt.Field(i)
}
// Field returns the i'th struct field.
func (t *structType) Field(i int) (f StructField) {
  // 溢出检查
    if i < 0 || i >= len(t.fields) {
        panic("reflect: Field index out of bounds")
    }
    // 获取之前structType中fields字段的值
    p := &t.fields[i]
  // 转换成StructFiled构造体
    f.Type = toType(p.typ)
    f.Name = p.name.name()
  // 判断是否是匿名构造体
    f.Anonymous = p.embedded()
    if !p.name.isExported() {
        f.PkgPath = t.pkgPath.name()
    }
    if tag := p.name.tag(); tag != "" {
        f.Tag = StructTag(tag)
    }
  // 获取字段的偏移量
    f.Offset = p.offset()
  // 获取索引值
    f.Index = []int{i}
    return
}
StructField
// A StructField describes a single field in a struct.
type StructField struct {
   Name string // 字段名
   PkgPath string // 字段门路
   Type      Type      // 字段反射类型对象
   Tag       StructTag // 字段的构造体标签
   Offset    uintptr   // 字段在构造体中的绝对偏移
   Index     []int     // Type.FieldByIndex中的返回的索引值
   Anonymous bool      // 是否为匿名字段
}

到这里整个反射获取构造体成员信息的过程应该很清朗了吧~。

Go实现了structTypeStructField

总结

Go

欢送关注公众号:Golang梦工厂

举荐往期文章:

  • 学习channel设计:从入门到放弃
  • 编程模式之Go如何实现装璜器
  • Go语言中new和make你应用哪个来分配内存?
  • 源码分析panic与recover,看不懂你打我好了!
  • 空构造体引发的大型打脸现场
  • 面试官:两个nil比拟后果是什么?
  • 面试官:你能用Go写段代码判断以后零碎的存储形式吗?
  • 赏析Singleflight设计