A Question about `unsafe.Slice`

115 views
Skip to first unread message

rmfr

unread,
Feb 5, 2022, 9:08:06 PM2/5/22
to golang-nuts
Please take a look at the following code:

```go
package main

import (
    "fmt"
    "runtime"
    "unsafe"
)

func assert(b bool) {
    if b {
    } else {
        panic("unexpected")
    }
}

type Elementer interface {
    GetInt64Slice() []int64
    GetByteSlice() []byte
    String() string
}

type Element1 struct {
    int64s []int64
    bs     []byte
}

func newElement1(int64Sz, bsSz int) *Element1 {
    assert(int64Sz > 0 && bsSz > 0)
    len0 := int64Sz * 8
    len1 := bsSz
    buf := make([]byte, len0+len1)
    for i := range buf {
        buf[i] = byte(i)
    }
    e := &Element1{
        int64s: unsafe.Slice((*int64)(unsafe.Pointer(&buf[0])), int64Sz),
        bs:     unsafe.Slice((*byte)(&buf[len0]), bsSz),
    }
    return e
}

func (e *Element1) GetInt64Slice() []int64 {
    return e.int64s
}

func (e *Element1) GetByteSlice() []byte {
    return e.bs
}

func (e *Element1) String() string {
    return fmt.Sprintf("Element1:[% x % x]", e.GetInt64Slice(), e.GetByteSlice())
}

type Element2 struct {
    underlyBuf []byte  // <- is this field needed?
    int64s     []int64
    bs         []byte
}

func newElement2(int64Sz, bsSz int) *Element2 {
    assert(int64Sz > 0 && bsSz > 0)
    len0 := int64Sz * 8
    len1 := bsSz
    buf := make([]byte, len0+len1)
    for i := range buf {
        buf[i] = byte(i)
    }
    e := &Element2{
        underlyBuf: buf,   // <-
        int64s:     unsafe.Slice((*int64)(unsafe.Pointer(&buf[0])), int64Sz),
        bs:         unsafe.Slice((*byte)(&buf[len0]), bsSz),
    }
    return e
}

func (e *Element2) GetInt64Slice() []int64 {
    runtime.KeepAlive(e.underlyBuf)
    return e.int64s
}

func (e *Element2) GetByteSlice() []byte {
    runtime.KeepAlive(e.underlyBuf)
    return e.bs
}

func (e *Element2) String() string {
    runtime.KeepAlive(e.underlyBuf)
    return fmt.Sprintf("Element2:[% x % x]", e.GetInt64Slice(), e.GetByteSlice())
}

func main() {
    var e Elementer
    fmt.Println("print with host endian:")
    e = newElement1(2, 4)
    fmt.Println("hi", e)
    e = newElement2(2, 4)
    fmt.Println("hi", e)
}
```

And which yields:

```
hi Element1:[[ 706050403020100  f0e0d0c0b0a0908] 10 11 12 13]
hi Element2:[[ 706050403020100  f0e0d0c0b0a0908] 10 11 12 13]
```

I'm not sure whether the `underlyBuf` filed in `Element2` is needed or not. When I'm reading the doc from `https://pkg.go.dev/reflect#SliceHeader` there is:

> Moreover, the Data field is not sufficient to guarantee the data it references will not be garbage collected, so programs must keep a separate, correctly typed pointer to the underlying data.

But in `https://pkg.go.dev/unsafe#Slice` there are no such comments. So the question:

Which implementation of `Elementer` is correct? `Element2` or both of them?

Thanks a lot for your help.

rmfr

unread,
Feb 5, 2022, 9:33:35 PM2/5/22
to golang-nuts
It suddenly occurred to me that it was very like `re-slice` —— as long as part of the underlying array is referenced, the whole underlying array is never gc-ed. I guess there is a method in gc which could locate the whole underlying array as long as one address inside the array's address interval was provided? So now I tend to believe that `Element1` is correct too, and the `underlyBuf` filed in `Element2` is redundant and is not quite necessary, am I correct?

rmfr

unread,
Feb 5, 2022, 9:51:37 PM2/5/22
to golang-nuts
I found the comments of `findObject` in the runtime source:

```go
// findObject returns the base address for the heap object **containing**
// the address p, the object's span, and the index of the object in s.
// If p does not point into a heap object, it returns base == 0.
//
// If p points is an invalid heap pointer and debug.invalidptr != 0,
// findObject panics.
//
// refBase and refOff optionally give the base address of the object
// in which the pointer p was found and the byte offset at which it
// was found. These are used for error reporting.
//
// It is nosplit so it is safe for p to be a pointer to the current goroutine's stack.
// Since p is a uintptr, it would not be adjusted if the stack were to move.
//go:nosplit
func findObject(p, refBase, refOff uintptr) (base uintptr, s *mspan, objIndex uintptr)
```

Reply all
Reply to author
Forward
0 new messages