如有错误,欢迎大家批评指正~
一个小例子
package main
import (
"fmt"
)
type HelloInterface interface {
SayHello() string
}
type HelloStruct struct {
Name string
Number int
}
func (h HelloStruct) SayHello() string {
return "hello" + h.Name
}
func main() {
var h interface{}
h = HelloStruct{
Name: "world",
Number: 123,
}
ih, ok := h.(HelloInterface)
fmt.Println(ok, ih.SayHello())
}
/*
* go build -gcflags "-N -l" -o main main.go // 禁止内联禁止优化
$ go tool objdump -s "main.main" main
*/
对应的汇编
main.go:20 0x4810c0 4c8d6424a0 LEAQ -0x60(SP), R12
main.go:20 0x4810c5 4d3b6610 CMPQ 0x10(R14), R12
main.go:20 0x4810c9 0f86cc010000 JBE 0x48129b
main.go:20 0x4810cf 4881ece0000000 SUBQ $0xe0, SP
main.go:20 0x4810d6 4889ac24d8000000 MOVQ BP, 0xd8(SP)
main.go:20 0x4810de 488dac24d8000000 LEAQ 0xd8(SP), BP
main.go:21 0x4810e6 440f117c2458 MOVUPS X15, 0x58(SP)
main.go:22 0x4810ec 440f11bc24a0000000 MOVUPS X15, 0xa0(SP)
main.go:22 0x4810f5 48c78424b000000000000000 MOVQ $0x0, 0xb0(SP)
main.go:23 0x481101 488d0d72780100 LEAQ 0x17872(IP), CX
main.go:23 0x481108 48898c24a0000000 MOVQ CX, 0xa0(SP)
main.go:23 0x481110 48c78424a800000005000000 MOVQ $0x5, 0xa8(SP)
main.go:24 0x48111c 48c78424b00000007b000000 MOVQ $0x7b, 0xb0(SP)
main.go:22 0x481128 488d05b1e90000 LEAQ 0xe9b1(IP), AX
main.go:22 0x48112f 488d9c24a0000000 LEAQ 0xa0(SP), BX
main.go:22 0x481137 e84487f8ff CALL runtime.convT(SB)
main.go:22 0x48113c 4889442430 MOVQ AX, 0x30(SP)
main.go:22 0x481141 488d0d98e90000 LEAQ 0xe998(IP), CX
main.go:22 0x481148 48894c2458 MOVQ CX, 0x58(SP)
main.go:22 0x48114d 4889442460 MOVQ AX, 0x60(SP)
main.go:26 0x481152 440f117c2478 MOVUPS X15, 0x78(SP)
main.go:26 0x481158 488b5c2458 MOVQ 0x58(SP), BX
main.go:26 0x48115d 488b4c2460 MOVQ 0x60(SP), CX
main.go:26 0x481162 488d05d7bb0000 LEAQ 0xbbd7(IP), AX
main.go:26 0x481169 e8928af8ff CALL runtime.assertE2I2(SB)
main.go:26 0x48116e 4889442478 MOVQ AX, 0x78(SP)
// 省略暂时不关心的部分汇编代码
main.go:20 0x4812a0 e8fb9bfdff CALL runtime.morestack_noctxt.abi0(SB)
main.go:20 0x4812a5 e916feffff JMP main.main(SB)
上述汇编中重点关注
CALL runtime.
convT
(SB)
非接口转换为接口 会拷贝数据
CALL runtime.
assertE2I2
(SB)
接口断言
接口底层长啥样
// runtime/runtime2.go
// 包含方法的接口实现
type iface struct {
tab *itab
data unsafe.Pointer
}
// 不包含接口的方法实现
type eface struct {
_type *_type
data unsafe.Pointer
}
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.
type itab struct {
inter *interfacetype // 接口定义的类型信息
_type *_type // 接口实际指向值的类型信息
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
// 这里长度看着虽然为 1 但是当接口中方法大于1时,数据紧接着放在该区域的后面
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
runtime.convT
非接口转换为接口
非接口转换为接口的过程中 会拷贝一份数据
// The conv and assert functions below do very similar things.
// The convXXX functions are guaranteed by the compiler to succeed.
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
// The convXXX functions succeed on a nil input, whereas the assertXXX
// functions fail on a nil input.
// convT converts a value of type t, which is pointed to by v, to a pointer that can
// be used as the second word of an interface value.
func convT(t *_type, v unsafe.Pointer) unsafe.Pointer {
if raceenabled {
raceReadObjectPC(t, v, getcallerpc(), abi.FuncPCABIInternal(convT))
}
if msanenabled {
msanread(v, t.size)
}
if asanenabled {
asanread(v, t.size)
}
// 分配内存
x := mallocgc(t.size, t, true)
// 将原来的数据copy一份
typedmemmove(t, x, v)
return x
}
assertE2I2
接口断言实现
// 对应于 ih := h.(HelloInterface) 可能会发生panic
func assertE2I(inter *interfacetype, t *_type) *itab {
if t == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
}
return getitab(inter, t, false)
}
// 对应于 ih, ok := h.(HelloInterface) 不会发生panic
func assertE2I2(inter *interfacetype, e eface) (r iface) {
t := e._type
if t == nil {
return
}
// 找到对应的tab结构体
tab := getitab(inter, t, true)
if tab == nil {
return
}
r.tab = tab
// 数据部分是复用的
r.data = e.data
return
}
基于接口定义的实际类型(即示例代码中HelloInterface)和不包含方法的接口eface(即示例代码中的h)转换成iface(带方法的接口 ih)上述关键步骤为getitab
(拿到对应的itab结构) 并组装出eface,可以看出上述接口转换为接口,数据没有进行拷贝。
所以下面重点看getitab在干啥?
getitab
:找到对应的tab
从缓存中拿一个或者new一个itab结构体返回
- 无锁通过atomic相关操作从缓存中获取
- 上述失败则加互斥锁 再次尝试获取
- 缓存中不存在 则新生成一个
const itabInitSize = 512
var (
itabLock mutex // lock for accessing itab table
itabTable = &itabTableInit // pointer to current table
// 初始化长度为512的数组 存放itab缓存
itabTableInit = itabTableType{size: itabInitSize} // starter table
)
// Note: change the formula in the mallocgc call in itabAdd if you change these fields.
type itabTableType struct {
size uintptr // length of entries array. Always a power of 2.
count uintptr // current number of filled entries.
entries [itabInitSize]*itab // really [size] large
}
// iter 接口定义的类型信息
// typ 接口实际指向值的类型信息
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
if len(inter.mhdr) == 0 {
throw("internal error - misuse of itab")
}
// easy case
if typ.tflag&tflagUncommon == 0 {
if canfail {
return nil
}
name := inter.typ.nameOff(inter.mhdr[0].name)
panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
}
var m *itab
// First, look in the existing table to see if we can find the itab we need.
// This is by far the most common case, so do it without locks.
// Use atomic to ensure we see any previous writes done by the thread
// that updates the itabTable field (with atomic.Storep in itabAdd).
t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
if m = t.find(inter, typ); m != nil {
goto finish
}
// Not found. Grab the lock and try again.
lock(&itabLock)
if m = itabTable.find(inter, typ); m != nil {
unlock(&itabLock)
goto finish
}
// Entry doesn't exist yet. Make a new entry & add it.
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
// The hash is used in type switches. However, compiler statically generates itab's
// for all interface/type pairs used in switches (which are added to itabTable
// in itabsinit). The dynamically-generated itab's never participate in type switches,
// and thus the hash is irrelevant.
// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
m.hash = 0
m.init() // 耗时操作在初始化环节 如果是直接读的缓存会很快
itabAdd(m) // 放入全局的itab缓存 内部会判断如果负载因子>0.75 会两倍扩容
unlock(&itabLock)
finish:
// m.fun[0] ==0 代表类型typ 没有实现对应接口inter
if m.fun[0] != 0 {
return m
}
if canfail {
return nil
}
// this can only happen if the conversion
// was already done once using the , ok form
// and we have a cached negative result.
// The cached result doesn't record which
// interface function was missing, so initialize
// the itab again to get the missing function name.
panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}
Itab.init: 初始化
重点看 itab的初始化 这是断言耗时环节之一
// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
inter := m.inter
typ := m._type
x := typ.uncommon()
// both inter and typ have method sorted by name,
// and interface names are unique,
// so can iterate over both in lock step;
// the loop is O(ni+nt) not O(ni*nt).
// 方法集均做了排序 迭代复杂度为 O(ni+nt)
ni := len(inter.mhdr)
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
var fun0 unsafe.Pointer
imethods:
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
itype := inter.typ.typeOff(i.ityp)
name := inter.typ.nameOff(i.name)
iname := name.name()
ipkg := name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ {
t := &xmhdr[j]
tname := typ.nameOff(t.name)
if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
// 要么该方法是可导出的 要么两者位于同一个package下
if tname.isExported() || pkgPath == ipkg {
if m != nil {
ifn := typ.textOff(t.ifn)
if k == 0 {
fun0 = ifn // we'll set m.fun[0] at the end
} else {
methods[k] = ifn
}
}
continue imethods
}
}
}
// didn't find method
// 没有实现接口中的方法 提前返回 赋值 m.fun[0] = 0
// 也就是说 m.fun[0] == 0 ==》 该itab没有实现对应的接口
m.fun[0] = 0
return iname
}
// 这里的意思是 将接口中的第一个方法保存在fun[0]
m.fun[0] = uintptr(fun0)
return ""
}
itab.add: 放入全局的 itabTable
那么生成的 itab是如何 放入全局数组itabTable中的呢?
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
// compiler has provided some good hash codes for us.
return uintptr (inter.typ.hash ^ typ.hash)
}
// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
// See comment in find about the probe sequence.
// Insert new itab in the first empty spot in the probe sequence.
// hask冲突解决方法: 开放地址法之二次探测法
mask := t.size - 1
h := itabHashFunc(m.inter, m._type) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
m2 := *p
if m2 == m {
// A given itab may be used in more than one module
// and thanks to the way global symbol resolution works, the
// pointed-to itab may already have been inserted into the
// global 'hash'.
return
}
if m2 == nil {
// Use atomic write here so if a reader sees m, it also
// sees the correctly initialized fields of m.
// NoWB is ok because m is not in heap memory.
// *p = m
// itab存放在非堆内存中
atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
t.count++
return
}
h += i
h &= mask
}
}
// runtime/proc.go中 调用该方法
// 也就是说在main启动的时候 会将收集到的活跃的itab事先放入到全局的itabTable中
// 用来加快运行时获取itab的性能
func itabsinit() {
lockInit(&itabLock, lockRankItab)
lock(&itabLock)
for _, md := range activeModules() {
for _, i := range md.itablinks {
itabAdd(i)
}
}
unlock(&itabLock)
}
find
: itab具体怎么寻找
传入的inter和typ 怎么寻找出 匹配的itab呢?
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
// Implemented using quadratic probing.
// Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
// We're guaranteed to hit all table entries using this probe sequence.
mask := t.size - 1
h := itabHashFunc(inter, typ) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
// Use atomic read here so if we see m != nil, we also see
// the initializations of the fields of m.
// m := *p
m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
if m == nil {
return nil
}
if m.inter == inter && m._type == typ {
return m
}
h += i
h &= mask
}
}
看完接口的大致布局,想必应该就能理解为什么 Go 不支持 []T 转换为 []interface?
The first is that a variable with type
[]interface{}
is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear. Well, is it? A variable with type[]interface{}
has a specific memory layout, known at compile time. Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type[]interface{}
is backed by a chunk of data that is N*2 words long. This is different than the chunk of data backing a slice with type[]MyType
and the same length. Its chunk of data will beN*sizeof(MyType)
words long. The result is that you cannot quickly assign something of type[]MyType
to something of type[]interface{}
; the data behind them just look different.
详细解释 mp.weixin.qq.com/s/cwDEgnicK…
反射
go的反射和上述的interface的底层实现息息相关,阅读以下的部分 期望解决以下疑惑
- 什么情况下 CanSet CanAddr 返回true
- Value 和Type 的Elem()代表什么意思
- 如何实现Value和interface的互转
- IsNil 理解
反射三大定律
强烈建议大家阅读官方 受益匪浅 茅塞顿开 醍醐灌顶
- Reflection goes from interface value to reflection object.
reflect.ValueOf(xxx) any -> Value反射对象
- Reflection goes from reflection object to interface value.
Value.Interface() Value反射对象 -> any
- To modify a reflection object, the value must be settable.
什么是Settability
Settability is a bit like addressability, but stricter. It’s the property that a reflection object can modify the actual storage that was used to create the reflection object. Settability is determined by whether the reflection object holds the original item. 关键句
总体转换图
graph TD
具体类型 --> any
any --> Value
Value --> any
any-->Type
Value --> Type
核心概念
Value
// reflect/Value.go
type Value struct {
// GO的世界中 表征类型的结构体
// 与 runtime/type.go 中结构体 _type 类型保持一致
typ *rtype
// 指向数据所在的地址 或者指向 指向数据的指针的地址
ptr unsafe.Pointer
// flag holds metadata about the value.
// 保存着 反射中用到的Kind标识,CanSet等信息
flag
}
Flag & Kind
-
0-4位
表征Value的具体类型(27种) 对标Kind()返回的类型枚举 -
第5位(
flagStickyRO
) 标识unexported not embedded field
只读 -
第6位(
flagEmbedRO
) 标识unexported embedded field
只读 -
第7位 (
flagIndir
):val holds a pointer to the data
标识Value.ptr
存放的是指向数据的指针的地址 -
第8位
flagAddr
:v.CanAddr is true
(implies flagIndir) -
...
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota // 0 invalid kind
Bool // 1
Int // 2
Int8 // ...
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
type flag uintptr
// flag holds metadata about the value.
// The lowest bits are flag bits:
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
// The next five bits give the Kind of the value.
// This repeats typ.Kind() except for method values.
// The remaining 23+ bits give a method number for method values.
// If flag.kind() != Func, code can assume that flagMethod is unset.
// If ifaceIndir(typ), code can assume that flagIndir is set.
const (
flagKindWidth = 5 // there are 27 kinds
flagKindMask flag = 1<<flagKindWidth - 1
flagStickyRO flag = 1 << 5
flagEmbedRO flag = 1 << 6
flagIndir flag = 1 << 7
flagAddr flag = 1 << 8
flagMethod flag = 1 << 9
flagMethodShift = 10
flagRO flag = flagStickyRO | flagEmbedRO
)
Type
// Type is the representation of a Go type.
//
// Not all methods apply to all kinds of types. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of type before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run-time panic.
//
// Type values are comparable, such as with the == operator,
// so they can be used as map keys.
// Two Type values are equal if they represent identical types.
type Type interface {
// Methods applicable to all types.
// 限于篇幅 省略了很多方法 ...
// 对应于rtype.kind & 31 取低5位
// Kind returns the specific kind of this type.
Kind() Kind
// Elem returns a type's element type.
// It panics if the type's Kind is not Ptr.
Elem() Type
common() *rtype
uncommon() *uncommonType
}
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
// 与runtime 期间的_type 保持一致
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
接下来咱们逐个击破~
具体类型转换为接口
s:=struct{xxxx}
var e interface{}
// 1. 显式赋值
e=s
// 2. 隐式赋值
// 因为ValueOf的接受参数是interface{}
// 会先将 s转换为interface(拷贝数据)然后再作为参数传递
relfect.ValueOf(s)
上述代码的1-4行可以理解为底层实现:runtime.convT
非接口转换为接口的过程中 会拷贝一份数据
// The conv and assert functions below do very similar things.
// The convXXX functions are guaranteed by the compiler to succeed.
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
// The convXXX functions succeed on a nil input, whereas the assertXXX
// functions fail on a nil input.
// convT converts a value of type t, which is pointed to by v, to a pointer that can
// be used as the second word of an interface value.
func convT(t *_type, v unsafe.Pointer) unsafe.Pointer {
if raceenabled {
raceReadObjectPC(t, v, getcallerpc(), abi.FuncPCABIInternal(convT))
}
if msanenabled {
msanread(v, t.size)
}
if asanenabled {
asanread(v, t.size)
}
// 分配内存
x := mallocgc(t.size, t, true)
// 将原来的数据copy一份 基于type进行拷贝
typedmemmove(t, x, v)
return x
}
// 优化: 接口底层针对一些特定的类型转换为接口
// 会调用不同的convXX 方法 优化转换效率(避免typedmemmove)
func convT64(val uint64) (x unsafe.Pointer) {
if val < uint64(len(staticuint64s)) {
// 优化小技巧: 针对 val< 256 复用`常量池` 而无需分配内存
x = unsafe.Pointer(&staticuint64s[val])
} else {
x = mallocgc(8, uint64Type, false)
*(*uint64)(x) = val
}
return
}
func convTstring(val string) (x unsafe.Pointer) {
if val == "" {
x = unsafe.Pointer(&zeroVal[0])
} else {
x = mallocgc(unsafe.Sizeof(val), stringType, true)
*(*string)(x) = val
}
return
}
// convTxxxx
接口转为Value类型
从代码来看接口转换为Value比较简单
- 如果
i==nil
则返回一个默认空Value{}
- 如果是传入
i
是指针类型,则设置上flagIndr
标志位,表明word存放的数据是 指向数据的指针的地址,而不是数据存放的地址 - 同时设置上该字段的
Kind
// reflect/value.go
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i any) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i) // 使用反射Value变量会逃逸到堆上
return unpackEface(i)
}
// Dummy annotation marking that the value x escapes,
// for use in cases where the reflect code is so clever that
// the compiler cannot follow.
func escapes(x any) {
if dummy.b {
dummy.x = x
}
}
var dummy struct {
b bool
x any
}
// emptyInterface is the header for an interface{} value.
// 空接口的内存布局
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
// 如果是一个指针 设置标志位flagIndir
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
Value类型转接口
// Interface returns v's current value as an interface{}.
// It is equivalent to:
//
// var i interface{} = (v's underlying value)
//
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i any) {
return valueInterface(v, true)
}
func valueInterface(v Value, safe bool) any {
// v.isValid()==false 调用该方法 会panic
if v.flag == 0 {
panic(&ValueError{"reflect.Value.Interface", Invalid})
}
// 不可导出字段或者方法 不允许通过该方法暴露
if safe && v.flag&flagRO != 0 {
// Do not allow access to unexported values via Interface,
// because they might be pointers that should not be
// writable or methods or function that should not be callable.
panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")
}
// 说明v.Kind == Func
if v.flag&flagMethod != 0 {
v = makeMethodValue("Interface", v)
}
// 接口类型 直接返回其指向的数据
if v.kind() == Interface {
// Special case: return the element inside the interface.
// Empty interface has one layout, all interfaces with
// methods have a second layout.
if v.NumMethod() == 0 {
return *(*any)(v.ptr)
}
return *(*interface {
M()
})(v.ptr)
}
// TODO: pass safe to packEface so we don't need to copy if safe==true?
return packEface(v)
}
packEface
// packEface converts v to the empty interface.
func packEface(v Value) any {
t := v.typ
var i any
e := (*emptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
switch {
case ifaceIndir(t):
if v.flag&flagIndir == 0 {
panic("bad indir")
}
// Value is indirect, and so is the interface we're making.
ptr := v.ptr
if v.flag&flagAddr != 0 {
// TODO: pass safe boolean from valueInterface so
// we don't need to copy if safe==true?
c := unsafe_New(t)
typedmemmove(t, c, ptr)
ptr = c
}
e.word = ptr
case v.flag&flagIndir != 0:
// Value is indirect, but interface is direct. We need
// to load the data at v.ptr into the interface data word.
e.word = *(*unsafe.Pointer)(v.ptr)
default:
// Value is direct, and so is the interface.
e.word = v.ptr
}
// Now, fill in the type portion. We're very careful here not
// to have any operation between the e.word and e.typ assignments
// that would let the garbage collector observe the partially-built
// interface value.
e.typ = t
return i
}
接口转为Type类型
返回实现类 *rtype
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
// 直接返回了 eface.typ
return toType(eface.typ)
}
Value转换为Type
// Type returns v's type.
func (v Value) Type() Type {
if v.flag != 0 && v.flag&flagMethod == 0 {
return v.typ
}
// 针对kind=Func 有一些处理 未深入研究
return v.typeSlow()
}
关键方法
CanAddr(可寻址的)
用于判断一个变量在反射中
是否可以获取其地址
- 切片中元素
- addressable数组 中的元素
- addressable 结构体中的字段
- 指针类型的解引用
// CanAddr reports whether the value's address can be obtained with Addr.
// Such values are called addressable. A value is addressable if it is
// an element of a slice, an element of an addressable array,
// a field of an addressable struct, or the result of dereferencing a pointer.
// If CanAddr returns false, calling Addr will panic.
func (v Value) CanAddr() bool {
return v.flag&flagAddr != 0
}
众所周知,go中参数传递都是值传递。而切片传递的是指针的拷贝,数组传递的是值的拷贝。所以上述针对数组类型才会特别强调addressable
var arr = [3]int{1, 2, 3}
// false 因为int基础类型 值传递,传入的值实际上是arr[0]的一份拷贝
// 所以认为传入的变量是不可寻值的(因为寻值没有意义,就算传入的变量有地址,也不是指向arr[0]本身)
fmt.Println(reflect.ValueOf(arr[0]).CanAddr())
// true 传入的是指向int的指针 还是指向arr[0]
fmt.Println(reflect.ValueOf(&arr[0]).Elem().CanAddr())
// false 因为传入的整个数组的拷贝 寻值无意义
fmt.Println(reflect.ValueOf(arr).Index(0).CanAddr())
// true 因为传入的指向数组的指针 底层的数据还是共用一份
fmt.Println(reflect.ValueOf(&arr).Index(0).CanAddr())
// true 切片传递的是指针 没有进行底层数据拷贝 所以其中的元素是寻址的
slice := []int{1, 23}
fmt.Println(reflect.ValueOf(slice).Index(0).CanAddr()) // true
CanSet
用于判断一个变量是否是可以设置的,即这个变量是否可以通过反射修改
在CanAddr的基础上 加一个判断: 需要这个变量是可导出字段
// CanSet reports whether the value of v can be changed.
// A Value can be changed only if it is addressable and was not
// obtained by the use of unexported struct fields.
// If CanSet returns false, calling Set or any type-specific
// setter (e.g., SetBool, SetInt) will panic.
func (v Value) CanSet() bool {
return v.flag&(flagAddr|flagRO) == flagAddr
}
import (
"fmt"
"reflect"
)
func main() {
test()
}
type h struct {
hh int64
}
func test() {
s := &h{}
// false hh不可导出 不允许修改的
fmt.Println(reflect.ValueOf(s).Elem().FieldByName("hh").CanSet())
// true 传入的指针类型,丢失了结构体的不可导出字段的元信息
// 所以认为是可以修改的
fmt.Println(reflect.ValueOf(&s.hh).Elem().CanSet())
}
Elem
Value.Elem
针对反射 Value实体的 Elem
仅可用于 Kind = Interface or Pointer
感觉该方法的作用 英文描述的精准些,故引用之
Elem returns the value that the interface v contains or that the pointer v points to.
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Pointer.
// It returns the zero Value if v is nil.
// 所以Elem()的实现是 对于rtype所在的内存地址
// 根据不同的Kind进行重翻译,返回其中的elem
// 语义是返回 类型的内在类型
// aka. 数组类型返回数组中元素的类型 map类型返回map 中value的类型
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface any
// 针对没有任何方法的接口 其内存布局是空接口 eface
if v.typ.NumMethod() == 0 {
eface = *(*any)(v.ptr)
} else {
// 非空接口 则是另一种内存布局
eface = (any)(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
// 透传只读属性标志 即该Value是只读的 其中指向的变量仍然是只读的
x.flag |= v.flag.ro()
}
return x
case Pointer:
ptr := v.ptr
// ptr 是指向数据的指针的指针 通过elem解引用 得到指向数组的指针
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
// v.typ 所在的内存区域 根据不同的Kind 进行不同的`翻译` 比如这里针对指针指针类型,
// 重新翻译为ptrType 从而取出 指针指向数据的Kind
// 这么做的好处就是可以极大地节省内存,各种Kind按照各自的内存布局进行翻译,
// 而不是使用大一统的结构体存储(会存在很多冗余字段,比如有些字段只有数组类型会用到,基础类型则不会使用)
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
// 注意: 这里 针对指针类型Elem后 设置flagIndir 和 flagAddr 标志位
// 这里也就解释了 为什么CanSet可以通过 flagAddr 标志位判断了
// 因为`指针类型`调用了 `Elem` 方法才会设置该标志位,且这个字段是可以往下透传的
// 存在该标志 可以理解为反射中 这个变量或者这个变量的`父容器` 曾经是通过`指针`解引用而来的,
// 所以改变这个变量是被允许的
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflectlite.Value.Elem", v.kind()})
}
Type.Elem
Elem returns a type's element type. It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
// ptrType represents a pointer type.
type ptrType struct {
rtype
elem *rtype // pointer element (pointed at) type
}
// sliceType represents a slice type.
type sliceType struct {
rtype
elem *rtype // slice element type
}
// 一样的套路 根据Kind 重新翻译
func (t *rtype) Elem() Type {
switch t.Kind() {
case Array:
tt := (*arrayType)(unsafe.Pointer(t))
return toType(tt.elem)
case Chan:
tt := (*chanType)(unsafe.Pointer(t))
return toType(tt.elem)
case Map:
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.elem)
case Pointer:
tt := (*ptrType)(unsafe.Pointer(t))
return toType(tt.elem)
case Slice:
tt := (*sliceType)(unsafe.Pointer(t))
return toType(tt.elem)
}
panic("reflect: Elem of invalid type " + t.String())
}
IsNil
isNil 比较实际上是 v.ptr(数据)区域是否为空。
而平常写代码
a==nil
只有类型和值都为空的时候才为真
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
func (v Value) IsNil() bool {
k := v.kind()
switch k {
case Chan, Func, Map, Pointer, UnsafePointer:
if v.flag&flagMethod != 0 {
return false
}
ptr := v.ptr
// flagIndir 含义见 flag章节
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
return ptr == nil
case Interface, Slice:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return *(*unsafe.Pointer)(v.ptr) == nil
}
panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}
穿插一个小问题
之前碰到一个关于marshal的case 困扰了我很久,猛然想起 反射中判空的逻辑,豁然开朗
type MyStruct struct {
a bool
b *b
}
type b struct {
c int32
any interface{}
}
type Inner struct {
Hello int64
}
type MyCoderV1 struct {
}
func (v1 MyCoderV1) Name() string {
return "v1"
}
func (v1 MyCoderV1) Marshal(v interface{}) ([]byte, error) {
str, err := json.Marshal(v)
return str, err
}
func (v1 MyCoderV1) Unmarshal(data []byte, v interface{}) error {
vv, _ := v.(**MyStruct)
*vv = &MyStruct{
b: &b{
// 这里好像必须传一个初始化好的结构体 才能marshal出对应的结构体
// 传入var inner *Inner(类型不为空,但是值为空) 最终还是会marshal成 map[string]interface{}
any: &Inner{},
},
}
return json.Unmarshal(data, vv)
}
Marshal中类型的set和判断肯定用的是反射 而反射的IsNil语义 是值不为空,所以上述场景 传入一个有类型但是没有值的指针var inner *Inner
不行,但是传入&Inner{}
可以。追踪源代码,验证了我的猜想~
// encoding/json/decode.go
// object consumes an object from d.data[d.off-1:], decoding into v.
// The first byte ('{') of the object has been read already.
func (d *decodeState) object(v reflect.Value) error {
// Check for unmarshaler.
u, ut, pv := indirect(v, false)
if u != nil {
start := d.readIndex()
d.skip()
return u.UnmarshalJSON(d.data[start:d.off])
}
if ut != nil {
d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
d.skip()
return nil
}
v = pv
t := v.Type()
// Decoding into nil interface? Switch to non-reflect code.
// 如果类型是接口 则调用objectInterface()
// 这个方法返回的便是map[string]interface{}
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
oi := d.objectInterface()
v.Set(reflect.ValueOf(oi))
return nil
}
// ... 省略部分代码
// objectInterface is like object but returns map[string]interface{}.
func (d *decodeState) objectInterface() map[string]any {}
func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
v0 := v
haveAddr := false
if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() {
haveAddr = true
v = v.Addr()
}
for {
// Load value from interface, but only if the result will be
// usefully addressable.
// 也就是说 传入未初始化的指针 不会进入这个条件分支调用Elem()方法
// 所以最终返回的Kind还是Interface
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) {
haveAddr = false
v = e
continue
}
}
// ... 省略部分代码
}