英文:
atomic.AddInt64 Causes invalid memory address or nil pointer dereference
问题
在调用结构体字段上的atomic.AddInt64时,会出现“无效的内存地址或空指针解引用”错误,但在重新排列字段顺序后就不会出现这个错误;为什么会这样呢?
使用以下类型:
type CountHandler struct {
c *RequestContext
count int64
}
调用atomic.AddInt64(&countHandler.count, 1)
(此时字段c
为nil)会导致错误。但是当我们将其重写为:
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))
}
英文:
Calling atomic.AddInt64 on field of a struct panics invalid memory address or nil pointer dereference
, but not when we re-arrange fields order; why?
Using this type:
type CountHandler struct {
c *RequestContext
count int64
}
And calling atomic.AddInt64(&countHandler.count, 1)
(field c
is nil at this point) panics. But not when we rewrite it as:
type CountHandler struct {
count int64
c *RequestContext
}
Error goes away.
I guess it should be so, because Go keeps data in memory in a sequential manner and reaching a nil
value breaks this sequence (of bytes); yet I wonder why is that so again because a pointer should have a fixed size nil
or other value.
This is Go x86 1.4.2 on Windows & complete error message is:
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
Whole source code is (this code is wrong, I was just about studying 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))
}
答案1
得分: 14
故障在第一种情况下是由于原子更新的字段未正确对齐引起的。
在ARM和x86-32上,调用者有责任确保以原子方式访问的64位字的64位对齐。全局变量或分配的结构体或切片中的第一个字可以保证是64位对齐的。
英文:
The fault is caused in the first case by the atomically updated field not being properly aligned.
> On both ARM and x86-32, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a global variable or in an allocated struct or slice can be relied upon to be 64-bit aligned.
答案2
得分: 1
以下是解决该问题的一些技巧,以防您遇到此错误:
最简单的方法是按照 OP 中的解释,将所有的 64 位原子值放在结构体的顶部:
c := struct {
val int64 // 位置 0
val2 int64 // 位置 8
valid bool // 位置 16
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1))
如果出于某种原因,您不想将该字段放在顶部,您可以在 64 位字段上方始终放置一个 _ [4]byte
,以确保它正确对齐。
c := struct {
val int64 // 位置 0
valid bool // 位置 8
_ [4]byte // 位置 9;编译器在位置 13 添加额外的 [3]byte 进行对齐
val2 int64 // 位置 16,正确对齐
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1)) // => 2
请注意,如果字段已经对齐,这种方法将不起作用;相反,如果之前没有发生 panic,现在将会发生 panic。
c := struct {
val int64 // 位置 0
_ [4]byte // 位置 8;编译器不添加填充
val2 int64 // 位置 12,不是 8 的倍数!
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1)) // => 运行时错误:无效的内存地址 [...]
您还可以依赖于切片中的 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
比处理原子操作更清晰和易懂。
英文:
Here are a few tricks to solve the problem in case you stumble upon this error:
The easiest way, as explained in OP, is to place all your 64-bit atomic values at the top of the struct:
c := struct {
val int64 // pos 0
val2 int64 // pos 8
valid bool // pos 16
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1))
If you, for whatever reason, don't want to place this field at the top, you can always place a _ [4]byte
above the 64-bit field to ensure it is correctly padded.
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
Note that this will not work in case the field was already aligned; on the contrary, if it didn't panic before, it will panic now.
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 [...]
You can also rely on the behaviour that the first element in slices of 64-bit elements will be correctly aligned:
c := struct {
val int64
valid bool
val2 []int64
}{val2: []int64{1}}
fmt.Println(atomic.AddInt64(&c.val2[0], 1))
Note that this won't work for arrays, as their values are stored in the struct directly and not on the heap as it is the case for slice data.
The last trick you can pull off is that of declaring the field in the struct as a pointer to an int64
; if the int64
it points to is aligned, then it will run smoothly.
c := struct {
val int64
valid bool
val2 *int64
}{val2: new(int64)}
fmt.Println(atomic.AddInt64(c.val2, 1))
If you don't want to get your hands dirty with sync/atomic
, remember that sync.Mutex
is a much cleaner and more understandable solution than dealing with atomics.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论