字节切片转换与不安全的字符串更改其地址

我有这个函数将字符串转换为字节切片而不复制


func StringToByteUnsafe(s string) []byte {

    strh := (*reflect.StringHeader)(unsafe.Pointer(&s))

    var sh reflect.SliceHeader

    sh.Data = strh.Data

    sh.Len = strh.Len

    sh.Cap = strh.Len

    return *(*[]byte)(unsafe.Pointer(&sh))

}

这工作正常,但是通过非常具体的设置会产生非常奇怪的行为:

设置在这里:https://github.com/leviska/go-unsafe-gc/blob/main/pkg/pkg_test.go

会发生什么情况:

  1. 创建字节切片

  2. 将其转换为临时(右值)字符串,并使用不安全再次将其转换为字节切片

  3. 然后,复制此切片(通过引用)

  4. 然后,用戈鲁丁内的第二个切片做点什么

  5. 打印前后的指针

我在我的linux mint笔记本电脑上有这个输出,带有go 1.16:

go test ./pkg -v -count=1

=== RUN   TestSomething

0xc000046720 123 0xc000046720 123

0xc000076f20 123 0xc000046721 z

--- PASS: TestSomething (0.84s)

PASS

ok      github.com/leviska/go-unsafe-gc/pkg     0.847s

因此,第一个切片神奇地改变了它的地址,而第二个切片则不是


如果我们删除 goroutine(并且可能稍微玩弄一下代码),我们可以获取两个指针来更改值(更改为同一个指针)。runtime.GC()


如果我们将不安全的强制转换更改为所有内容,则无需更改地址即可工作。此外,如果我们从这里将其更改为不安全的强制值,https://stackoverflow.com/a/66218124/5516391 一切工作原理相同。[]byte()


func StringToByteUnsafe(str string) []byte { // this works fine

    var buf = *(*[]byte)(unsafe.Pointer(&str))

    (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Cap = len(str)

    return buf

}

我运行它并得到相同的结果。我运行它,没有错误。GOGC=off-race


如果将其作为具有 main 函数的主包运行,它似乎可以正常工作。此外,如果您删除该函数。我的猜测是编译器在这种情况下会优化内容。Convert

所以,我对此有几个问题:

  1. 这到底是怎么回事?看起来像一个奇怪的UB

  2. 为什么运行时会神奇地更改变量的地址?

  3. 为什么在无并发情况下它可以更改两个地址,而在并发情况下不能?

  4. 这个不安全的强制转换与堆栈溢出答案的强制转换之间有什么区别?为什么它有效?

或者这只是一个编译器错误?


慕码人2483693
浏览 74回答 1
1回答

三国纷争

来自github问题的回答 https://github.com/golang/go/issues/47247的后备存储是在堆栈上分配的,因为它不会转义。戈鲁丁堆栈可以动态移动。另一方面,b逃逸到堆中,因为它被传递给另一个戈鲁廷。通常,我们不会假设对象的地址不会更改。这按预期工作。我的版本不正确,因为它使用反射。切片标题作为普通结构。你可以对它进行兽医检查,去兽医会警告你。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go