使用不安全的[]byte转换为string在go中可能会导致哪些后果?

4
首选将[]byte转换为string的方法是这样的:
var b []byte
// fill b
s := string(b)

在这段代码中,复制了字节切片,这在性能重要的情况下可能会造成问题。
当性能至关重要时,可以考虑执行不安全转换:
var b []byte
// fill b
s :=  *(*string)(unsafe.Pointer(&b))

我的问题是:在使用不安全的转换时有什么可能出错的地方?我知道 string 应该是不可变的,如果我们改变了 bs 也会被改变。但是,这样做会有什么坏处吗?

1
永远不要使用不安全的代码。性能从来不是那么关键。 - Volker
1个回答

5

修改语言规范保证为不可变的内容是背叛的行为。

由于规范保证string是不可变的,编译器可以生成基于此的代码缓存其值并进行其他优化。您无法以任何正常方式更改string的值,如果您采取不正当的方式(如使用unsafe包)仍要这样做,则会失去规范提供的所有保证,继续使用修改后的string,您可能会遇到“错误”和随机的意外情况。

例如,如果您将string用作映射中的键,并在将其放入映射之后更改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)
    }
}

输出(在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

起初,我们只看到了一些奇怪的结果:简单的Println()无法找到其值。它看到了某些东西(找到了键),但是值显示为nil,这甚至不是值类型int的有效值(int的零值为0)。
如果我们将地图扩大到很大(添加1000个元素),地图的内部数据结构将被重组。之后,即使我们使用适当的键显式地请求该值,我们仍然无法找到它。它仍然在地图中,因为我们迭代所有键值对时会找到它,但由于哈希代码随着string的值而改变,因此最有可能在与它所在的桶(或应该在的桶)不同的桶中搜索它。
此外,请注意,使用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)

我们使用s创建了2个新的本地变量s2s3s2通过复制s的字符串头进行初始化,而s3则是用相同内容但具有新的string值(新的字符串头)进行初始化。现在,如果您修改原始的s,则在正确的程序中,将新字符串与原始字符串进行比较,无论是true还是false,您都会得到相同的结果(基于是否缓存值,但应该相同)。
但输出结果是(在Go Playground上尝试):
hi hi hi
true
false

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接