Pap*_*ter 4 string pointers unsafe go
[]byte转换为的首选方式string是:
var b []byte
// fill b
s := string(b)
Run Code Online (Sandbox Code Playgroud)
在此代码中复制字节片,这在性能很重要的情况下可能会出现问题。
当性能至关重要时,可以考虑执行不安全转换:
var b []byte
// fill b
s := *(*string)(unsafe.Pointer(&b))
Run Code Online (Sandbox Code Playgroud)
我的问题是:使用不安全转换时会出现什么问题?我知道这string应该是不可变的,如果我们改变b,s它也会改变。但仍然:那又怎样?难道这一切都可能发生吗?
修改语言规范保证不可变的内容是一种叛国行为。
由于规范保证strings 是不可变的,因此允许编译器生成缓存其值的代码,并基于此进行其他优化。您不能string以任何正常方式更改 s 的值,如果您采用肮脏的方式(如 package unsafe)仍然这样做,您将失去规范提供的所有保证,并且通过继续使用修改后的strings,您可能会随机地遇到“错误”和意想不到的事情。
例如,如果您使用 astring作为映射中的键,并且string在将其放入映射后更改了,则您可能无法使用原始值或修改后的值string(这是依赖于实现)。
为了演示这一点,请参见以下示例:
m := map[string]int{}
b := []byte("hi")
s := *(*string)(unsafe.Pointer(&b))
m[s] = 999
fmt.Println("Before:", m)
b[0] = 'b'
fmt.Println("After:", m)
fmt.Println("But it's there:", m[s], m["bi"])
for i := 0; i < 1000; i++ {
m[strconv.Itoa(i)] = i
}
fmt.Println("Now it's GONE:", m[s], m["bi"])
for k, v := range m {
if k == "bi" {
fmt.Println("But still there, just in a different bucket: ", k, v)
}
}
Run Code Online (Sandbox Code Playgroud)
输出(在Go Playground上尝试):
Before: map[hi:999]
After: map[bi:<nil>]
But it's there: 999 999
Now it's GONE: 0 0
But still there, just in a different bucket: bi 999
Run Code Online (Sandbox Code Playgroud)
起初,我们只是看到一些奇怪的结果:simplePrintln()无法找到它的值。它看到了一些东西(找到了键),但值显示为nil甚至不是值类型的有效值( isint的零值)。int0
如果我们将地图变大(添加 1000 个元素),地图的内部数据结构就会被重组。此后,我们甚至无法通过使用适当的密钥显式请求来找到我们的值。当迭代我们找到它的所有键值对时,它仍然在映射中,但由于哈希码随着值的变化而变化string,很可能在与它所在的位置(或它应该在的位置)不同的存储桶中搜索它是)。
另请注意,使用 package 的代码unsafe可能会像您现在期望的那样工作,但是相同的代码在 Go 的未来(或旧)版本中可能会完全不同(意味着它可能会崩溃),因为“导入不安全的包可能是不可移植的,并且不受 Go 1 兼容性指南的保护”。
此外,您可能会遇到意外错误,因为修改后的内容string可能以不同的方式使用。有人可能只是复制字符串标题,有人可能复制其内容。看这个例子:
b := []byte{'h', 'i'}
s := *(*string)(unsafe.Pointer(&b))
s2 := s // Copy string header
s3 := string([]byte(s)) // New string header but same content
fmt.Println(s, s2, s3)
b[0] = 'b'
fmt.Println(s == s2)
fmt.Println(s == s3)
Run Code Online (Sandbox Code Playgroud)
我们创建了 2 个新的局部变量s2并s3使用s,s2通过复制 的字符串标头进行初始化s,并s3使用新string值(新字符串标头)进行初始化,但内容相同。现在,如果您修改原始字符串s,您会期望在正确的程序中将新字符串与原始字符串进行比较,您将得到相同的结果,无论是 或true(false基于是否缓存了值,但应该是相同的)。
但输出是(在Go Playground上尝试一下):
hi hi hi
true
false
Run Code Online (Sandbox Code Playgroud)