为了账号安全,请及时绑定邮箱和手机立即绑定

atomic.AddInt64 导致无效的内存地址或 nil 指针取消引用

atomic.AddInt64 导致无效的内存地址或 nil 指针取消引用

Go
繁华开满天机 2021-09-13 20:03:08
在 struct panics 的字段上调用 atomic.AddInt64 invalid memory address or nil pointer dereference,但在我们重新排列字段顺序时不会;为什么?使用这种类型:type CountHandler struct {    c     *RequestContext    count int64}并且调用atomic.AddInt64(&countHandler.count, 1)(此时字段c为零)恐慌。但当我们将其重写为:type CountHandler struct {    count int64    c     *RequestContext}错误消失。我想应该是这样,因为 Go 以顺序方式将数据保存在内存中,并且达到一个nil值会破坏这个(字节)序列;但我想知道为什么又是这样,因为指针应该具有固定大小nil或其他值。这是 Windows 上的 Go x86 1.4.2,完整的错误消息是:2015/02/23 12:56:44 http: panic serving [::1]:51886: runtime error: invalid memory address or nil pointer dereferencegoroutine 5 [running]:net/http.func·011()        c:/go/src/net/http/server.go:1130 +0xa8sync/atomic.AddUint64(0x731144, 0x1, 0x0, 0x0, 0x263168)        c:/go/src/sync/atomic/asm_386.s:118 +0xcmain.(*CountHandler).ServeHTTP(0x731140, 0x263180, 0x122f6380, 0x122f62a0)        C:/Workshop/Devox/Workshop-Go/src/geoho/web/app/app.go:62 +0x42github.com/julienschmidt/httprouter.func·001(0x263180, 0x122f6380, 0x122f62a0, 0x0, 0x0, 0x0)        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:232 +0x4cgithub.com/julienschmidt/httprouter.(*Router).ServeHTTP(0x122d5d20, 0x263180, 0x122f6380, 0x122f62a0)        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:298 +0x141net/http.serverHandler.ServeHTTP(0x122d2280, 0x263180, 0x122f6380, 0x122f62a0)        c:/go/src/net/http/server.go:1703 +0x145net/http.(*conn).serve(0x122e01e0)        c:/go/src/net/http/server.go:1204 +0x9d8created by net/http.(*Server).Serve        c:/go/src/net/http/server.go:1751 +0x2ce
查看完整描述

2 回答

?
慕运维8079593

TA贡献1876条经验 获得超5个赞

在第一种情况下,错误是由原子更新的字段没有正确对齐引起的。

在 ARM 和 x86-32 上,调用者负责安排以原子方式访问的 64 位字的 64 位对齐。全局变量或分配的结构体或切片中的第一个字可以依赖于 64 位对齐。


查看完整回答
反对 回复 2021-09-13
?
繁星淼淼

TA贡献1775条经验 获得超11个赞

如果您偶然发现此错误,这里有一些解决问题的技巧:


如 OP 中所述,最简单的方法是将所有 64 位原子值放在结构的顶部:


c := struct {

    val   int64 // pos 0

    val2  int64 // pos 8

    valid bool  // pos 16

}{val2: 1}

fmt.Println(atomic.AddInt64(&c.val2, 1))

如果您出于某种原因不想将此字段放在顶部,则始终可以_ [4]byte在 64 位字段上方放置 a以确保正确填充它。


c := struct {

    val   int64   // pos 0

    valid bool    // pos 8

    _     [4]byte // pos 9; compiler adds additional [3]byte at pos 13 for alignment

    val2  int64   // pos 16, correctly aligned

}{val2: 1}

fmt.Println(atomic.AddInt64(&c.val2, 1)) // => 2

请注意,如果字段已经对齐,这将不起作用;相反,如果它之前没有恐慌,它现在就会恐慌。


c := struct {

    val   int64   // pos 0

    _     [4]byte // pos 8; compiler adds no padding

    val2  int64   // pos 12, not a multiple of 8!

}{val2: 1}

fmt.Println(atomic.AddInt64(&c.val2, 1)) // => runtime error: invalid memory address [...]

您还可以依赖 64 位元素切片中的第一个元素将正确对齐的行为:


c := struct {

    val   int64

    valid bool

    val2  []int64

}{val2: []int64{1}}

fmt.Println(atomic.AddInt64(&c.val2[0], 1))

请注意,这不适用于数组,因为它们的值直接存储在结构中,而不是像切片数据那样存储在堆中。


您可以使用的最后一个技巧是将结构中的字段声明为指向int64;的指针。如果int64它指向的是对齐的,那么它会顺利运行。


c := struct {

    val   int64

    valid bool

    val2  *int64

}{val2: new(int64)}

fmt.Println(atomic.AddInt64(c.val2, 1))

如果你不想弄脏你的手sync/atomic,请记住,这sync.Mutex是一个比处理原子更清晰、更容易理解的解决方案。


查看完整回答
反对 回复 2021-09-13
  • 2 回答
  • 0 关注
  • 304 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信