string - 在 go 中使用从 []byte 到 string 的不安全转换可能产生的后果是什么?

标签 string pointers go unsafe

[]byte 转换为 string 的首选方法是这样的:

var b []byte
// fill b
s := string(b)

在此代码中,字节 slice 被复制,这在性能很重要的情况下可能是个问题。

当性能很关键时,可以考虑执行不安全的转换:

var b []byte
// fill b
s :=  *(*string)(unsafe.Pointer(&b))

我的问题是:使用不安全转换会出现什么问题?我知道 string 应该是不可变的,如果我们更改 bs 也会更改。仍然:那又怎样?可能发生的都是坏事吗?

最佳答案

修改语言规范保证不可变的东西是叛国行为。

由于规范保证 string 是不可变的,因此允许编译器生成缓存其值的代码,并基于此进行其他优化。您不能以任何正常方式更改 string 的值,并且如果您诉诸肮脏的方式(如 package unsafe)仍然这样做,您将失去所有保证由规范提供,并继续使用修改后的 string,您可能会随机遇到“错误”和意想不到的事情。

例如,如果您使用 string 作为 map 中的键,并且在将其放入 map 后更改 string,您可能无法找到 map 中的关联值使用 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)。

如果我们将 map 变大(我们添加 1000 个元素), map 的内部数据结构将被重组。在此之后,我们甚至无法通过使用适当的键明确请求它来找到我们的值(value)。它仍然在 map 中迭代我们找到它的所有键值对,但由于哈希码随着 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通过复制字符串头来初始化ss3 使用新的 string 值(新字符串 header )初始化,但内容相同。现在,如果您修改原始 s,您会期望在一个正确的程序中将新字符串与原始字符串进行比较,您会得到相同的结果,无论是 true 还是 false(基于值是否被缓存,但应该相同)。

但输出是(在 Go Playground 上尝试):

hi hi hi
true
false

关于string - 在 go 中使用从 []byte 到 string 的不安全转换可能产生的后果是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33952378/

相关文章:

java - 如何在第一个逗号之前拆分字符串?

c - 如何在没有指针地址的情况下将结构指针分配为零并在下面的代码中查找结构的 sizeof ?

C 二维数组 : Is the first 'level' an array of pointers?

google-app-engine - 如何找到数据存储中的最大值?

反射:按字符串构造

javascript - 不要在循环的最后一次迭代中添加字符串

PHP 字符串到十六进制/字节?

c# - 将地址对象转换为字符串的最佳方法是什么?

c - 指向 C 中数组的第一个元素

go - 使用位于数据存储中其他包中的(子)结构