0%

Golang不可不知的十大陷阱(二)- gob decode会把指向0的指针变为nil

俗话说得好,golang一时爽,一直golang一直爽.

最近公司也在从Python逐渐迁往Golang,在迁移的过程中不得不感叹这世界还有golang这么吊吊的语言,完全就是爱用就用不用就拉到的架势,让人又爱又恨.

今天开始我们来慢慢盘点下踩到的那些神级坑。

第二章,我们来探索下gob是怎么把指向0的指针变为nil。


日常搬砖中,我们常常会做一些类似于deepcopy的事情,例如:

  • 把别人struct的相同key的value原封不动的copy到自己的struct
  • 从缓存中拿到bytes然后反序列化到自己的struct中 这时候无可避免的你会用到golang官方的gob(如果你用第三方库那另说)

我们先定义一个struct

1
2
3
4
type DemoStruct struct {
Demo1 *int32
Demo2 *int32
}

然后用一段代码模拟我们从缓存,例如Redis里边拿到的bytes

1
2
3
4
5
6
7
8
9
b := DemoStruct{
proto.Int32(0),
proto.Int32(1),
}
fmt.Println("原始数据")
fmt.Println(b)
buf := new(bytes.Buffer)
_ = gob.NewEncoder(buf).Encode(b)
// 用上述代码来模拟从Redis中拿到的bytes:buf

最后用官方的gob做反序列化

1
2
3
4
5
// 现在反序列化到我们想要的DemoStruct中
var c DemoStruct
_ = gob.NewDecoder(buf).Decode(&c)
fmt.Println("反序列化后")
fmt.Println(c)

完整代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"bytes"
"encoding/gob"
"fmt"
"github.com/golang/protobuf/proto"
)

type DemoStruct struct {
Demo1 *int32
Demo2 *int32
}

func main() {
b := DemoStruct{
proto.Int32(0),
proto.Int32(1),
}
fmt.Println("原始数据")
fmt.Println(b)
buf := new(bytes.Buffer)
_ = gob.NewEncoder(buf).Encode(b)
// 用上述代码来模拟从Redis中拿到的bytes:buf

// 现在反序列化到我们想要的DemoStruct中
var c DemoStruct
_ = gob.NewDecoder(buf).Decode(&c)
fmt.Println("反序列化后")
fmt.Println(c)
}

我们的期望值肯定是c和b一模一样是不是?

如果你run上述代码最后发现自己拿到的是

1
2
3
4
原始数据
{0xc00001816c 0xc000018270}
反序列化后
{<nil> 0xc000018478}

指向1的指针依然可以被原封不动的反序列化回去,但是指向0的指针会被反序列化为nil。

这就是我们今天的主题,所以当利用gob做反序列化的时候一定要小心,切不可以为自己丢到Redis里边的值永远不会为nil,拿到的时候就永远不会为nil,这个地方 一定要做相应的判断!!!!不然会直接panic

你可能以为这是golang的一个bug?nonono,这是golang的正常行为。 详见:

https://golang.org/pkg/encoding/gob/

留意这么一句

Structs are sent as a sequence of (field number, field value) pairs. The field value is sent using the standard gob encoding for its type, recursively. If a field has the zero value for its type (except for arrays; see above), it is omitted from the transmission. The field number is defined by the type of the encoded struct: the first field of the encoded type is field 0, the second is field 1, etc. When encoding a value, the field numbers are delta encoded for efficiency and the fields are always sent in order of increasing field number; the deltas are therefore unsigned. The initialization for the delta encoding sets the field number to -1, so an unsigned integer field 0 with value 7 is transmitted as unsigned delta = 1, unsigned value = 7 or (01 07). Finally, after all the fields have been sent a terminating mark denotes the end of the struct. That mark is a delta=0 value, which has representation (00).

简而言之,这不是个bug,这就是golang官方的约定。

such is golang.