golang类型转换
插图由 玛丽亚·莱塔(MariaLetta)/ free-gophers-pack组成 ,Renee French 创作的地鼠 。
警告! 您应该避免在应用程序中使用不安全的方法,除非您100%确定自己在做什么。 出于某种原因,它被称为不安全。
Go是强类型的,您必须将变量从一种类型转换为另一种类型,才能在应用程序的不同部分中使用。 但是有时您需要绕过这种类型的安全性。 在每个滴答声都很重要的高负载系统中,可能需要优化瓶颈。 不安全的操作可能会使许多分配安全。 不安全还允许入侵任何结构字段,包括切片,字符串,映射等。
更改字符串示例
字符串在Go中是不可变的。 如果要更改字符串,通常必须分配一个新字符串。 让我们用不安全的软件包破解一点,然后实际更改字符串!
package main
import (
"fmt"
"reflect"
"time"
"unsafe"
)
func main () {
a := "Hello. Current time is " + time.Now().String()
fmt.Println(a)
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))
*(* byte )(unsafe.Pointer(stringHeader.Data + 5 )) = '!'
fmt.Println(a)
}
结果:
Hello. Current time is 2020-03-14 21:40:38.36328248 +0300 +03 m=+0.000037994
Hello! Current time is 2020-03-14 21:40:38.36328248 +0300 +03 m=+0.000037994
字符串改变了!
程式码片段- https://play.golang.org/p/QJ6hIMz6_gg
这段代码中发生了什么? 步骤如下:
- 将字符串指针转换为
unsafe.Pointer
。 任何指针都可以转换为unsafe.Pointer
,反之亦然。
unsafe.Pointer
也可以转换为uintptr
整数形式的地址。 - 将
unsafe.Pointer
转换为reflect.StringHeader
指针,以获取Data指针。
reflect.StringHeader
类型反映字符串内部运行时结构。 在uintptr
字符串由长度值和指向数据的uintptr
指针表示。 - 将指针向前移动5个字节。 使用
uintptr
可以执行指针算术。 - 将新的
uintptn
转换为unsafe.Pointer
,然后将其转换为byte
指针。 该字节实际上是字符串的一部分。 - 通过指针将新值分配给字节。 字符串改变了!
顺便说一句,以下代码将产生细分违规错误。 Go编译器了解字符串是常量,并将其与其他不允许更改的常量一起放入内存。 这就是为什么我在第一个示例中使用time.Now()
的原因。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main () {
a := "Hello. Have a nice day!"
fmt.Println(a)
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))
*(* byte )(unsafe.Pointer(stringHeader.Data + 5 )) = '!'
fmt.Println(a)
}
结果:
Hello. Have a nice day!
unexpected fault address 0x4c3e31
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x4c3e31 pc=0x48cf33]
代码段-https: //play.golang.org/p/ix70l38QvA4
包裹不安全
https://golang.org/pkg/unsafe/
软件包仅包含3个功能和单个可用类型。
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
type Pointer *ArbitraryType
type ArbitraryType
// represents the type of an arbitrary Go expression,
// not actually part of a package
Offsetof
给出struct中字段的偏移量。 Sizeof
是内存中变量的大小,不包括引用的内存。 Alignof
提供有关变量地址对齐的信息。
使用unsafe.Offsetof
和unsafe.Sizeof
函数的一些示例:
- 不安全的数组迭代和类型转换快速分配为零。
- 获取有关内存中结构实际大小的信息。
- 使用结构指针和字段偏移量直接在内存中更改结构字段。
package main
import (
"fmt"
"unsafe"
)
type Bytes400 struct {
val [ 100 ] int32
}
type TestStruct struct {
a [ 9 ] int64
b byte
c *Bytes400
d int64
}
func main () {
array := [ 10 ] uint64 { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }
var sum int8
// Unsafe array iteration
sizeOfUint64 := unsafe.Sizeof(array[ 0 ])
for i := uintptr ( 0 ); i < 10 ; i++ {
sum += *(* int8 )(unsafe.Pointer( uintptr (unsafe.Pointer(&array)) + sizeOfUint64*i))
}
fmt.Println(sum)
// Size of struct and offsets of struct fields
t := TestStruct{b: 42 }
fmt.Println(unsafe.Sizeof(t))
fmt.Println(unsafe.Offsetof(t.a), unsafe.Offsetof(t.b), unsafe.Offsetof(t.c), unsafe.Offsetof(t.d))
fmt.Println(unsafe.Sizeof(Bytes400{}))
// Change struct field t.b value
*(* byte )(unsafe.Pointer( uintptr (unsafe.Pointer(&t)) + unsafe.Offsetof(t.b)))++
fmt.Println(t.b)
}
结果:
55
88
0 72 76 80
400
43
代码段-https: //play.golang.org/p/cSmAUdu3sS9
指针算术和垃圾收集器
一个unsafe.Pointer
可以转换为uintptr
,反之亦然。 执行指针算术的唯一方法是使用uintptr
。 一般规则是uintptr
是没有指针语义的整数值。 将uintptr
用作指向对象的唯一指针是不安全的。 垃圾回收器不知道它,并且对象内存可以重用。 算术运算完成后,最好立即将其转换回unsafe.Pointer
。
不安全字符串到[] byte转换的基准
package main
import (
"reflect"
"unsafe"
)
func SafeBytesToString (bytes [] byte ) string {
return string (bytes)
}
func SafeStringToBytes (s string ) [] byte {
return [] byte (s)
}
func UnsafeBytesToString (bytes [] byte ) string {
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
return *(* string )(unsafe.Pointer(&reflect.StringHeader{
Data: sliceHeader.Data,
Len: sliceHeader.Len,
}))
}
func UnsafeStringToBytes (s string ) [] byte {
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
return *(*[] byte )(unsafe.Pointer(&reflect.SliceHeader{
Data: stringHeader.Data,
Len: stringHeader.Len,
Cap: stringHeader.Len,
}))
}
gotest -bench=.
基准结果:
BenchmarkSafeBytesToString-8 257141380 4.50 ns/op
BenchmarkSafeStringToBytes-8 227980887 5.38 ns/op
BenchmarkUnsafeBytesToString-8 1000000000 0.305 ns/op
BenchmarkUnsafeStringToBytes-8 1000000000 0.274 ns/op
不安全的转换速度快10倍以上! 但是此转换仅分配新的标头,数据保持不变。 因此,如果在转换后更改切片,字符串也会更改。 以下测试将通过:
func TestUnsafeBytesToString (t *testing.T) {
bs := [] byte ( "Test" )
str := UnsafeBytesToString(bs)
if str != "Test" {
t.Fail()
}
// Test string mutation
bs[ 0 ] = 't'
if str != "test" {
t.Fail()
}
}
如果您确实需要加快转换速度或侵入go结构内部,请使用不安全的软件包。 在其他情况下,尽量避免它,将它弄乱也不难。
感谢您的阅读,并祝您安全愉快!
回购代码示例:
https://github.com/kochetkov-av/go-unsafe-type-conversions
翻译自: https://hackernoon.com/golang-unsafe-type-conversions-and-memory-access-odz3yrl
golang类型转换