Is the following funciton always safe to convert a string to a slice?

232 views
Skip to first unread message

T L

unread,
Apr 21, 2020, 9:48:08 AM4/21/20
to golang-nuts
func String2ByteSlice(str string) (bs []byte) {
    strHdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
    sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
   
    // Is it possible that the str value is allocated on stack
    // and the stack grows at this moment, so the address value
    // stored in strHdr.Data will become obsoleted?
   
    sliceHdr.Data = strHdr.Data
    sliceHdr.Len = strHdr.Len
    sliceHdr.Cap = strHdr.Len
   
    runtime.KeepAlive(&str)
   
    return
}

T L

unread,
Apr 21, 2020, 11:16:51 AM4/21/20
to golang-nuts
And is the runtime.KeepAlive call in the following program essential to keep the correctness?

package main

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

func main() {
    a := [6]byte{'G', 'o', 'o', 'g', 'l', 'e'}
    bs := []byte("Golang")
    hdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
    hdr.Data = uintptr(unsafe.Pointer(&a))
   
    runtime.KeepAlive(&a) // Is this line essential here?
   
    hdr.Len = 2
    hdr.Cap = len(a)
    fmt.Printf("%s\n", bs) // Go
    bs = bs[:cap(bs)]
    fmt.Printf("%s\n", bs) // Google
}

Ian Lance Taylor

unread,
Apr 21, 2020, 8:11:49 PM4/21/20
to T L, golang-nuts
Since you are correctly using *reflect.StringHeader, it doesn't matter
whether the stack is copied at that moment. You are pointing at the
actual string value, so the pointer will be adjusted. As far as I can
see, this is safe. (Of course, it's not safe if somebody modifies the
contents of the returned []byte.)

Ian

Ian Lance Taylor

unread,
Apr 21, 2020, 8:13:17 PM4/21/20
to T L, golang-nuts
On Tue, Apr 21, 2020 at 8:17 AM T L <tapi...@gmail.com> wrote:
>
> And is the runtime.KeepAlive call in the following program essential to keep the correctness?
>
> package main
>
> import (
> "fmt"
> "unsafe"
> "reflect"
> "runtime"
> )
>
> func main() {
> a := [6]byte{'G', 'o', 'o', 'g', 'l', 'e'}
> bs := []byte("Golang")
> hdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
> hdr.Data = uintptr(unsafe.Pointer(&a))
>
> runtime.KeepAlive(&a) // Is this line essential here?
>
> hdr.Len = 2
> hdr.Cap = len(a)
> fmt.Printf("%s\n", bs) // Go
> bs = bs[:cap(bs)]
> fmt.Printf("%s\n", bs) // Google
> }

I do not think the runtime.KeepAlive is required. Setting the Data
field using a *SliceHeader should keep the value alive.

Ian

T L

unread,
Apr 22, 2020, 7:33:22 AM4/22/20
to golang-nuts
Many thanks for the answers.

I have another question, which doesn't matter with or without the runtime.KeepAlive call.
We know that assignment in Go is not atomic.
Is it possible that when the assignment "hdr.Data = uintptr(unsafe.Pointer(&a))" is half done,
the stack is resized, so the address of a might be changed (assume a is allocated on stack),
the hdr.Data will be assigned the old invalid value?

T L

unread,
Apr 22, 2020, 10:08:12 AM4/22/20
to golang-nuts
I understand that, by the case 6 in the unsafe package docs,
the second program should be safe with or without the runtime.KeepAlive call.
I just wonder how Go runtime achieves this goal.
I have an impression that pointer assignments in Go by gc is atomically,
but is this also true for uintptr value assignments?

Ian Lance Taylor

unread,
Apr 22, 2020, 4:42:35 PM4/22/20
to T L, golang-nuts
First, let me say that it's best to avoid the word "atomic" without
clearly explaining what you mean, as it has many different meanings
and implications. In this case I think what you mean is that in Go an
assignment made by one goroutine may be interleaved with an assignment
made by a different goroutine. Or, more precisely, you mean that a
goroutine may be interrupted in the middle of doing an assignment,
such that another goroutine may observe the assignment as started but
not fully completed.

Technically, I think you are correct: I don't know of anything in the
Go language spec that prohibits a goroutine from being interrupted
while partway through the assignment to hdr.Data. But then the Go
language spec doesn't discuss stack resizing at all.

In practice no Go implementation is going to interrupt an assignment
that is only a single memory word. And, of course, it would be
invalid for a Go implementation that does stack shrinking or garbage
collection to interrupt an assignment of a pointer value. And at the
machine level, on a modern processor, an assignment of a uintptr uses
the exact same instructions as an assignment of an unsafe.Pointer. So
I don't think there is anything to worry about here.

Ian

Ian Lance Taylor

unread,
Apr 22, 2020, 4:44:18 PM4/22/20
to T L, golang-nuts
On Wed, Apr 22, 2020 at 7:08 AM T L <tapi...@gmail.com> wrote:
>
> I understand that, by the case 6 in the unsafe package docs,
> the second program should be safe with or without the runtime.KeepAlive call.
> I just wonder how Go runtime achieves this goal.
> I have an impression that pointer assignments in Go by gc is atomically,
> but is this also true for uintptr value assignments?

Using the meaning of "atomic" from my previous message, then, yes,
both pointer assignments and uintptr assignments are done in a way
that can not be interrupted. That more or less happens automatically,
as it's a single machine instruction either way. And in fact it's the
same machine instruction, since pointers and uintptr are the same size
and use the same registers.

Ian
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/2b9b1bec-4176-421f-8820-a6ff2bf2ead2%40googlegroups.com.

Robert Engels

unread,
Apr 22, 2020, 5:43:49 PM4/22/20
to Ian Lance Taylor, T L, golang-nuts
But it’s a data race regardless. Even if the runtime did interrupt the go routine mid write and change the stack it would be forced to eventually correct the value to have the write be valid - but if another routine is reading that value, including the GC, there must be synchronization added. In the case of GC it is internal - for a user go routine it must be explicit.

> On Apr 22, 2020, at 3:44 PM, Ian Lance Taylor <ia...@golang.org> wrote:
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcVd6cjuu0Sv9SpgqcnZ8cEG%2B6kXpbLQz70VL8Jr%2B5VeBg%40mail.gmail.com.

T L

unread,
Apr 24, 2020, 9:05:55 AM4/24/20
to golang-nuts
Many thanks. This is quite informative.
> To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.

T L

unread,
Apr 24, 2020, 11:54:11 AM4/24/20
to golang-nuts
For the first example, I always thought the "runtime.KeepAlive" call is essential before.
But after reading the explanations from Bryan C. Mills on Slack, I changed my mind now.

Bryan C. Mills said: The GC tracks pointers through their original allocations — the pointer scan does not care about lexical types, only allocation types.
Reply all
Reply to author
Forward
0 new messages