【问题标题】:atomic.AddInt64 Causes invalid memory address or nil pointer dereferenceatomic.AddInt64 导致无效的内存地址或 nil 指针取消引用
【发布时间】:2015-04-24 13:36:31
【问题描述】:

在结构体的字段上调用 ​​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 dereference
goroutine 5 [running]:
net/http.func·011()
        c:/go/src/net/http/server.go:1130 +0xa8
sync/atomic.AddUint64(0x731144, 0x1, 0x0, 0x0, 0x263168)
        c:/go/src/sync/atomic/asm_386.s:118 +0xc
main.(*CountHandler).ServeHTTP(0x731140, 0x263180, 0x122f6380, 0x122f62a0)
        C:/Workshop/Devox/Workshop-Go/src/geoho/web/app/app.go:62 +0x42
github.com/julienschmidt/httprouter.func·001(0x263180, 0x122f6380, 0x122f62a0, 0x0, 0x0, 0x0)
        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:232 +0x4c
github.com/julienschmidt/httprouter.(*Router).ServeHTTP(0x122d5d20, 0x263180, 0x122f6380, 0x122f62a0)
        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:298 +0x141
net/http.serverHandler.ServeHTTP(0x122d2280, 0x263180, 0x122f6380, 0x122f62a0)
        c:/go/src/net/http/server.go:1703 +0x145
net/http.(*conn).serve(0x122e01e0)
        c:/go/src/net/http/server.go:1204 +0x9d8
created by net/http.(*Server).Serve
        c:/go/src/net/http/server.go:1751 +0x2ce

整个源代码是(这段代码错了,我刚研究alice):

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "github.com/justinas/alice"
    "net/http"

    "os"
    "sync/atomic"
)

// play with alice
func main() {
    c1 := alice.New(Counter, Texter).Then(nil)

    router := httprouter.New()
    router.Handler("GET", "/", c1)
    router.GET("/kill", kill)

    http.ListenAndServe(":27007", router)
}

func kill(rw http.ResponseWriter, rq *http.Request, pl httprouter.Params) {
    os.Exit(0)
}

var ch CountHandler

// constructors:

func Counter(h http.Handler) http.Handler {
    return &ch
}

func Texter(h http.Handler) http.Handler {
    var t TextHandler
    switch x := h.(type) {
    case *CountHandler:
        t.c = x.c
        t.text = fmt.Sprintf("called so far %d", atomic.LoadInt64(&x.count))
    }
    return &t
}

// handlers:

type RequestContext struct {
    val int
}

type CountHandler struct {
    c     *RequestContext
    count int64
}

func (c *CountHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    atomic.AddInt64(&c.count, 1)
}

type TextHandler struct {
    c    *RequestContext
    text string
}

func (t *TextHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    rw.Write([]byte(t.text))
}

【问题讨论】:

  • 对我来说似乎是一个错误。我拿走了你的代码,在 Linux 上它运行得很好。操场上也一样。看到这个:play.golang.org/p/fDFaZPi3nf这个确切的代码在你的 Windows 机器上崩溃了吗?
  • 是的,它应该可以工作。请发布更多代码,例如如何创建 countHandler 变量。请瞄准MCVE
  • 谢谢;我已经添加了代码。

标签: pointers go memory-management atomic


【解决方案1】:

第一种情况是由于原子更新的字段没有正确aligned引起的。

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

【讨论】:

  • 哦,不错,我错过了 x86 部分!
【解决方案2】:

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

如 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))

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

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 是一种比处理原子更简洁、更易于理解的解决方案。

【讨论】:

    猜你喜欢
    • 2013-09-16
    • 2019-12-12
    • 2020-04-17
    • 2015-06-18
    • 2017-03-05
    • 2019-04-19
    • 2017-03-18
    • 2014-01-12
    • 2014-01-29
    相关资源
    最近更新 更多