我有这个函数将字符串转换为字节切片而不复制
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
会发生什么情况:
创建字节切片
将其转换为临时(右值)字符串,并使用不安全再次将其转换为字节切片
然后,复制此切片(通过引用)
然后,用戈鲁丁内的第二个切片做点什么
打印前后的指针
我在我的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
所以,我对此有几个问题:
这到底是怎么回事?看起来像一个奇怪的UB
为什么运行时会神奇地更改变量的地址?
为什么在无并发情况下它可以更改两个地址,而在并发情况下不能?
这个不安全的强制转换与堆栈溢出答案的强制转换之间有什么区别?为什么它有效?
或者这只是一个编译器错误?
三国纷争
相关分类