Go语言反射 1

看别人写的反射代码,真的是看一遍忘一遍,难受...但是吧,该学还的学。

Go语言的反射,这里从一个问题说起吧。

偶然在代码中看到这样一段代码,如下:

// Body body decoder
func Body(r io.Reader, obj interface{}) error {
	if w, ok := obj.(io.Writer); ok {
		_, err := io.Copy(w, r)
		return err
	}

	all, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
    
    // LoopElem 不停地对指针解引用
	value := core.LoopElem(reflect.ValueOf(obj))

	if value.Kind() == reflect.String {
		value.SetString(core.BytesToString(all))
		return nil
	}

	if _, ok := value.Interface().([]byte); ok {
		value.SetBytes(all)
		return nil
	}

	fn, ok := convertBodyFunc[value.Kind()]
	if ok {
		return fn.cb(core.BytesToString(all), fn.bitSize, emptyField, value)
	}

	return fmt.Errorf("type (%T) %s", value, core.ErrUnknownType)
}

其实这是一个开源的go http client [github.com/guonaihong/gout]。

在代码21行有一个[]byte判断,前面还有一个解索引。

第一反应,obj传入一个[]byte类型应该没问题,因为断言判断的就是[]byte类型,core.LoopElem只是不断的解引用,好像没有影响。core.LoopElem如下:

// LoopElem 不停地对指针解引用
func LoopElem(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return v
		}
		v = v.Elem()
	}

	return v
}

但是测试时发现obj传入[]byte类型时却报错了,“using unaddressable value”。通过日志查看了一下调用栈,value.SetBytes(all)报错了。

错误信息很明显,“不可寻址的值”,因为本来就不是指针当然不能寻址了。但是明明前面的代码里写了这句,这不明显在判断类型为[]byte嘛,为啥又不行,自行车不要了?

if _, ok := value.Interface().([]byte); ok {
		value.SetBytes(all)
		return nil
	}

最后跟踪下来发现在SetBytes里有个函数mustBeAssignableSlow进行了指针判断

在下面代码第9行有过一个if f&flagAddr == 0判断

func (f flag) mustBeAssignableSlow() {
	if f == 0 {
		panic(&ValueError{methodName(), Invalid})
	}
	// Assignable if addressable and not read-only.
	if f&flagRO != 0 {
		panic("reflect: " + methodName() + " using value obtained using unexported field")
	}
	if f&flagAddr == 0 {
		panic("reflect: " + methodName() + " using unaddressable value")
	}
}

那v.Elem()到底在取什么,不是解索引吗,以C语言的解索引来看,解索引就是取地址指向的值,返回肯定不再是指针了。那就继续看v.Elem()到底在干什么吧。先直接看答案吧

func (v Value) Elem() Value {
	k := v.kind()
	switch k {
	case Interface:
		....
	case Ptr:
		ptr := v.ptr
		if v.flag&flagIndir != 0 {
			ptr = *(*unsafe.Pointer)(ptr)
		}
		// The returned value's address is v's value.
		if ptr == nil {
			return Value{}
		}
		tt := (*ptrType)(unsafe.Pointer(v.typ))
		typ := tt.elem
		fl := v.flag&flagRO | flagIndir | flagAddr
		fl |= flag(typ.Kind())
		return Value{typ, ptr, fl}
	}
	panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

v.Elem()虽然解索引了,但是Value的flag字段仍然将flagAddr 位置了1。指针仍然有效,这也是为啥传入[]byte不行了。

参考

[1] github.com/guonaihong/gout

[2]  gorm 深度剖析using unaddressable value_djqueen的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值