Golang中encoding/json包的nil指针解引用错误问题探讨
Golang中encoding/json包的nil指针解引用错误问题探讨 大家好,
我们正在使用 Go 语言(版本 1.21)的 json 编码器,有时在序列化一个非常简单的结构体时会遇到以下空指针错误:
goroutine 1619784 [running]:
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
encoding/json.(*encodeState).marshal.func1()
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x5580e728fe0b]
/usr/lib/golang/src/encoding/json/encode.go:293 +0x6d
/usr/lib/golang/src/encoding/json/encode.go:321 +0x73
encoding/json.stringEncoder(0xc00378c080, {0x5580e7e1aea0?, 0xc003d06200?, 0xc002b1600a?}, {0x19?, 0x0?})
/usr/lib/golang/src/encoding/json/encode.go:704 +0x21e
/usr/lib/golang/src/encoding/json/encode.go:321 +0x73
encoding/json.ptrEncoder.encode({0x5580e7d912c8?}, 0xc00378c080, {0x5580e7dfc600?, 0xc000e22cc0?, 0x5580e7dfc600?}, {0x10?, 0x0?})
/usr/lib/golang/src/encoding/json/encode.go:876 +0x23c
/usr/lib/golang/src/encoding/json/encode.go:847 +0xcf
encoding/json.structEncoder.encode({{{0xc0018d8240, 0x1, 0x1}, 0xc001880480, 0xc0018804b0}}, 0xc00378c080, {0x5580e7ead360?, 0xc003d06200?, 0xc003d06200?}, {0x0, ...})
encoding/json.(*encodeState).marshal(0xc001b319c8?, {0x5580e7dfc600?, 0xc000e22cc0?}, {0x2f?, 0x0?})
encoding/json.ptrEncoder.encode({0x7f06d8fc28b8?}, 0xc00378c080, {0x5580e7df0540?, 0xc003d06200?, 0x5580e7df0540?}, {0x60?, 0xce?})
/usr/lib/golang/src/encoding/json/encode.go:876 +0x23c
encoding/json.arrayEncoder.encode({0xc0021ba360?}, 0xc00378c080, {0x5580e7e2f0c0?, 0xc000e22cd0?, 0x5580e7e1aea0?}, {0x9?, 0x0?})
/usr/lib/golang/src/encoding/json/encode.go:658 +0xba
/usr/lib/golang/src/encoding/json/encode.go:960 +0xab
panic({0x5580e7e7e7a0?, 0x5580e86125a0?})
/usr/lib/golang/src/runtime/panic.go:770 +0x132
encoding/json.(*encodeState).reflectValue(0xc00378c080, {0x5580e7df0540?, 0xc003d06200?, 0x5580e727c825?}, {0x40?, 0x16?})
encoding/json.interfaceEncoder(0xc00378c080, {0x5580e7e50fe0?, 0xc000e22cd0?, 0x1?}, {0xa0?, 0xae?})
encoding/json.structEncoder.encode({{{0xc000b40008, 0x3, 0x4}, 0xc0008a6f00, 0xc0008a71a0}}, 0xc00378c080, {0x5580e7f02cc0?, 0xc000e22cc0?, 0xc000e22cc0?}, {0x0, ...})
/usr/lib/golang/src/encoding/json/encode.go:589 +0x3c7
encoding/json.appendString[...]({0xc001eec0b6?, 0x10?, 0x7f0720605f48?}, {0x0?, 0x40}, 0x1)
encoding/json.(*encodeState).reflectValue(0xc00378c080, {0x5580e7dfc600?, 0xc000e22cc0?, 0x12?}, {0x0?, 0xa8?})
/usr/lib/golang/src/encoding/json/encode.go:704 +0x21e
/usr/lib/golang/src/encoding/json/encode.go:704 +0x21e
github.com/gorilla/rpc/json.EncodeClientRequest({0x5580e79f5565, 0x17}, {0x5580e7df0540, 0xc003d06200})
我们使用 encoding/json/json.Marshal() 来序列化一个非常简单的函数局部结构体:
&clientRequest{
Method: method,
Params: [1]interface{}{args},
Id: uint64(rand.Int63()),
}
Params 是一个只包含一个元素的数组,该元素是一个内部只有一个字符串的非常简单的结构体(该字符串永远不会是 nil,因为它不是指针)。
我们还同时从多个 Go 协程中调用这个序列化逻辑。
到目前为止,我们无法将此错误的根源定位到我们代码的任何问题上。 因此,我们想看看您是否能就可能导致此类错误的原因给我们一些提示/建议?我们是否误用了 json 编码器 API?这可能是某种数据竞争吗?
提前感谢。
更多关于Golang中encoding/json包的nil指针解引用错误问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
检查 Method 的值。
你也可以使用 go run -race your_program.go 来检查代码中的数据竞争情况…
func main() {
fmt.Println("hello world")
}
更多关于Golang中encoding/json包的nil指针解引用错误问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
或许需要更多 Go 版本信息?

你好 @Rmarian,
有两点快速的想法:
-
你写道:“Params 是一个只包含一个元素的数组,这个元素是一个非常简单的结构体,里面只有一个字符串。” 你为什么要使用
any(也就是interface{})类型的数组,而不是实际结构体类型的数组呢? -
考虑添加一个安全检查,例如:
params := [1]interface{}{args} if params[0] == nil { ...
这可能有助于揭示导致空指针恐慌的原因。
这是一个典型的并发访问导致的nil指针解引用问题。从堆栈跟踪可以看出,错误发生在encoding/json.stringEncoder函数中,这表明在序列化字符串字段时遇到了nil指针。
问题很可能出现在clientRequest结构体的Method字段上。当多个goroutine同时访问和修改同一个结构体实例时,可能会发生数据竞争,导致Method字段在序列化过程中变为nil。
以下是问题复现的示例代码:
package main
import (
"encoding/json"
"fmt"
"sync"
)
type clientRequest struct {
Method string
Params [1]interface{}
Id uint64
}
func main() {
var wg sync.WaitGroup
req := &clientRequest{
Method: "testMethod",
Params: [1]interface{}{"testParam"},
Id: 123,
}
// 模拟并发访问
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 并发修改Method字段
req.Method = "newMethod"
// 并发序列化
_, err := json.Marshal(req)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}()
}
wg.Wait()
}
更常见的情况是,如果clientRequest结构体被多个goroutine共享并修改,就会出现数据竞争。正确的做法是为每个goroutine创建独立的结构体实例:
func safeMarshal(method string, args interface{}) ([]byte, error) {
req := &clientRequest{
Method: method,
Params: [1]interface{}{args},
Id: uint64(rand.Int63()),
}
return json.Marshal(req)
}
// 在每个goroutine中调用
go func() {
data, err := safeMarshal("methodName", args)
// 处理结果
}()
如果必须共享结构体,需要使用互斥锁保护:
type SafeClientRequest struct {
mu sync.RWMutex
req *clientRequest
}
func (s *SafeClientRequest) MarshalJSON() ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return json.Marshal(s.req)
}
检查代码中是否存在以下情况:
- 多个goroutine共享同一个
clientRequest实例 - 在序列化过程中同时修改结构体字段
- 使用全局或共享的变量存储
clientRequest实例
使用go run -race命令运行程序可以帮助检测数据竞争问题。

