可以做渗透的网站制作网页改颜色

当前位置: 首页 > news >正文

可以做渗透的网站,制作网页改颜色,新乡辉县网站建设,沈阳网站关键词优化哪里好现象 在日常开发中#xff0c;可能一不小心就会掉进 Go 语言的某些陷阱里#xff0c;而本文要介绍的 nil ≠ nil 问题#xff0c;便是其中一个#xff0c;初看起来会让人觉得很诡异#xff0c;摸不着头脑。 先来看个例子#xff1a; type CustomizedError struct {Err…现象 在日常开发中可能一不小心就会掉进 Go 语言的某些陷阱里而本文要介绍的 nil ≠ nil 问题便是其中一个初看起来会让人觉得很诡异摸不着头脑。 先来看个例子 type CustomizedError struct {ErrorCode intMsg string }func (e *CustomizedError) Error() string {return fmt.Sprintf(err code: %d, msg: %s, e.ErrorCode, e.Msg) } func main() {txn, err : startTx()if err ! nil {log.Fatalf(err starting tx: %v, err)}if err txn.doUpdate(); err ! nil {log.Fatalf(err updating: %v, err)}if err txn.commit(); err ! nil {log.Fatalf(err committing: %v, err)}fmt.Println(success!) }type tx struct{}func startTx() (*tx, error) {return tx{}, nil }func (*tx) doUpdate() *CustomizedError {return nil }func (*tx) commit() error {return nil } 这是一个简化过了的例子在上述代码中我们创建了一个事务然后做了一些更新在更新过程中如果发生了错误希望返回对应的错误码和提示信息。 看起来每个方法都会返回 nil应该能顺利走到最后一行输出 success 才对但实际上输出的却是 err updating: nil寻找原因 为什么明明返回的是 nil却被判定为 err ≠ nil 呢难道这个 nil 也有什么奇妙之处 这就需要我们来更深入一点了解 error 本身了。在 Go 语言中 error 是一个 interface 内部含有一个 Error() 函数返回一个字符串接口的描述如下 type error interface {Error() string }而对于一个变量来说它有两个要素一个是 type T一个是 value V如下图所示 来看一个简单的例子 var it interface{} fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // nil invalid reflect.Value it 1 fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // int 1 it hello fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // string hello var s *string it s fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string nil ss : hello it ss fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string 0xc000096560 在给一个 interface 变量赋值前T 和 V 都是 nil但给它赋值后不仅会改变它的值还会改变它的类型。 当把一个值为 nil 的字符串指针赋值给它后虽然它的值是 Vnil但它的类型 T 却变成了 *string。 此时如果拿它来跟 nil 比较结果就会是不相等因为只有当这个 interface 变量的类型和值都未被设置时它才真正等于 nil。 再来看看之前的例子中err 变量的 T 和 V 是如何变化的 func main() {txn, err : startTx()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))if err ! nil {log.Fatalf(err starting tx: %v, err)}if err txn.doUpdate(); err ! nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))log.Fatalf(err updating: %v, err)}if err txn.commit(); err ! nil {log.Fatalf(err committing: %v, err)}fmt.Println(success!) } 输出如下 nil invalid reflect.Value *err.CustomizedError nil在一开始我们给 err 初始化赋值时startTx 函数返回的是一个 error 接口类型的 nil。此时查看其类型 T 和值 V 时都会是 nil。 txn, err : startTx() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // nil invalid reflect.Valuefunc startTx() (*tx, error) {return tx{}, nil } 而在调用 doUpdate 时会将一个 *CustomizedError 类型的 nil 值赋值给了它它的类型 T 便成了 *CustomizedError V 是 nil。 err txn.doUpdate() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // *err.CustomizedError nil所以在做 err ≠ nil 的比较时err 的类型 T 已经不是 nil前面已经说过只有当一个接口变量的 T 和 V 同时为 nil 时这个变量才会被判定为 nil所以该不等式会判定为 true。 要修复这个问题其实最简单的方法便是在调用 doUpdate 方法时给 err 进行重新声明 if err : txn.doUpdate(); err ! nil {log.Fatalf(err updating: %v, err) }此时err 其实成了一个新的结构体指针变量而不再是一个interface 类型变量类型为 *CustomizedError 且值为 nil所以做 err ≠ nil 的比较时结果就是将是 false。 问题到这里似乎就告一段落了但再仔细想想就会发现这其中似乎还是漏掉了一环。 如果给一个 interface 类型的变量赋值时会同时改变它的类型 T 和值 V那跟 nil 比较时为什么不是跟它的新类型对应的 nil 比较呢 事实上interface 变量跟普通变量确实有一定区别一个非空接口 interface 即接口中存在函数方法初始化的底层数据结构是 iface一个空接口变量对应的底层结构体为 eface。 type iface struct {tab *itabdata unsafe.Pointer }type eface struct {_type *_typedata unsafe.Pointer }tab 中存放的是类型、方法等信息。data 指针指向的 iface 绑定对象的原始数据的副本。 再来看一下 itab 的结构 // 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 *_typehash uint32 // copy of type.hash. Used for type switches. [4]byte // 用于内存对齐fun [1]uintptr // variable sized. fun[0]0 means _type does not implement inter. } itab 中一共包含 5 个字段inner 字段存的是初始化 interface 时的静态类型。_type 存的是 interface 对应具体对象的类型当 interface 变量被赋值后这个字段便会变成被赋值的对象的类型。 itab 中的 _type 和 iface 中的 data 便分别对应 interface 变量的 T 和 V_type 是这个变量对应的类型data 是这个变量的值。在之前的赋值测试中通过 reflect.TypeOf 与 reflect.ValueOf 方法获取到的信息也分别来自这两个字段。 这里的 hash 字段和 _type 中存的 hash 字段是完全一致的这么做的目的是为了类型断言。 fun 是一个函数指针它指向的是具体类型的函数方法在这个指针对应内存地址的后面依次存储了多个方法利用指针偏移便可以找到它们。 再来看看 interfacetype 的结构 type interfacetype struct {typ _typepkgpath namemhdr []imethod }这其中也有一个 _type 字段来表示 interface 变量的初始类型。 看到这里之前的疑问便开始清晰起来一个 interface 变量实际上有两个类型一个是初始化时赋值时对应的 interface 类型一个是赋值具体对象时对象的实际类型。 了解了这些之后我们再来看一下之前的例子 txn, err : startTx()这里先对 err 进行初始化赋值此时它的 itab.inter.typ 对应的类型信息就是 error itab._type 仍为 nil。 err txn.doUpdate()当对 err 进行重新赋值时err 的 itab._type 字段会被赋值成 *CustomizedError 所以此时err 变量实际上是一个 itab.inter.typ 为 error 但实际类型为 *CustomizedError 值为 nil 的接口变量。 把一个具体类型变量与 nil 比较时只需要判断其 value 是否为 nil 即可而把一个接口类型的变量与 nil 进行比较时还需要判断其类型 itab._type 是否为nil。 如果想实际看看被赋值后 err 对应的 iface 结构可以把 iface 相关的结构体都复制到同一个包下然后通过 unsafe.Pointer 进行类型强转就可以通过打断点的方式来查看了。 func TestErr(t *testing.T) {txn, err : startTx()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))if err ! nil {log.Fatalf(err starting tx: %v, err)}p : (*iface)(unsafe.Pointer(err))fmt.Println(p.data)if err txn.doUpdate(); err ! nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))p : (*iface)(unsafe.Pointer(err))fmt.Println(p.data)log.Fatalf(err updating: %v, err)}if err txn.commit(); err ! nil {log.Fatalf(err committing: %v, err)}fmt.Println(success!) } 补充说明一下这里的inter.typ.kind 表示的是变量的基本类型其值对应 runtime 包下的枚举。 const (kindBool 1 iotakindIntkindInt8kindInt16kindInt32kindInt64kindUintkindUint8kindUint16kindUint32kindUint64kindUintptrkindFloat32kindFloat64kindComplex64kindComplex128kindArraykindChankindFunckindInterfacekindMapkindPtrkindSlicekindStringkindStructkindUnsafePointerkindDirectIface 1 5kindGCProg 1 6kindMask (1 5) - 1 ) 比如上图中所示的 kind 20 对应的类型就是 kindInterface。 总结 1.接口类型变量跟普通变量是有差异的非空接口类型变量对应的底层结构是 iface 空接口类型类型变量对应的底层结构是 eface。 2.iface 中有两个跟类型相关的字段一个表示的是接口的类型 inter一个表示的是变量实际类型 _type 。 3.只有当接口变量的 itab._type 与 data 都为 nil 时也就是实际类型和值都未被赋值前才真正等于 nil 。 到此一个有趣的探索之旅就结束了但长路漫漫前方还有无数的问题等待我们去探索和发现这便是学习的乐趣希望能与君共勉。