在背golang面试八股文时出现,经常会遇到一个说法:golang在强制类型转换时,会发生内存拷贝。为了验证该说法,本人做了一个实验验证,代码如下:
package main
import (
"fmt"
"reflect"
"strings"
"unsafe"
)
func main() {
str := "Hello, World!"
byteSlice := []byte(str)
fmt.Printf("String: %p\n", (*reflect.StringHeader)(unsafe.Pointer(&str)).Data)
fmt.Printf(" slice: %p\n", (*reflect.SliceHeader)(unsafe.Pointer(&byteSlice)).Data)
}
执行结果如下:
String: %!p(uintptr=2029802)
slice: %!p(uintptr=2029802)
明显,两者string和byte切片的底层指向的字符数组起始地址时一致的,这与一定会发生拷贝的现象描述并不符合。那么这种现象的具体原因是什么?
由于string底层指向的是只读的字符数组,是无法编辑,尝试对byte底层数组操作尝试下地址的变化:
package main
import (
"fmt"
"reflect"
"strings"
"unsafe"
)
func main() {
str := "Hello, World!"
byteSlice := []byte(str)
byteSlice = append(byteSlice, 'c')
fmt.Printf("String: %p\n", (*reflect.StringHeader)(unsafe.Pointer(&str)).Data)
fmt.Printf(" slice: %p\n", (*reflect.SliceHeader)(unsafe.Pointer(&byteSlice)).Data)
}
执行结果:
after String: %!p(uintptr=1050905)
after slice: %!p(uintptr=824634441728)
结果是byte切换指向的内存地址发生改变,这表明byte切片底层起始指向的byte数组跟string是同一个,编辑后发生了改变。
实际原因:是golang 编译器的优化。Go 1.22版本中(测试使用go版本为go 1.22),编译器对string到byte切片的转换进行了优化。这意味着当你将一个string转换为byte切片时,编译器可能会生成更高效的代码。
查看[]byte()转化的汇编
0x005f 00095 (D:/mycode/distribid-test/main.go:14) MOVQ main.str+40(SP), CX
0x0064 00100 (D:/mycode/distribid-test/main.go:14) MOVQ main.str+48(SP), DX
0x0069 00105 (D:/mycode/distribid-test/main.go:14) TESTQ CX, CX
0x006c 00108 (D:/mycode/distribid-test/main.go:14) LEAQ runtime.zerobase(SB), SI
0x0073 00115 (D:/mycode/distribid-test/main.go:14) CMOVQNE CX, SI
0x0077 00119 (D:/mycode/distribid-test/main.go:14) MOVQ SI, main.byteSlice+56(SP)
0x007c 00124 (D:/mycode/distribid-test/main.go:14) MOVQ DX, main.byteSlice+64(SP)
0x0081 00129 (D:/mycode/distribid-test/main.go:14) MOVQ DX, main.byteSlice+72(SP)
其中并没有申请内存的操作。
使用go 1.21版本测试,发现输出结果中指向的底层byte数组不一致,说明编译器并没有对转化操作进行优化
str is %!p(uintptr=13901539)
bytes is %!p(uintptr=824634171080)
[]byte()对应的汇编:
0x0089 00137 (D:/test/tesst.go:13) MOVUPS X15, main..autotmp_2+88(SP)
0x008f 00143 (D:/test/tesst.go:13) MOVQ main.bytes+104(SP), AX
0x0094 00148 (D:/test/tesst.go:13) PCDATA $1, $4
0x0094 00148 (D:/test/tesst.go:13) CALL runtime.convT64(SB)
0x0099 00153 (D:/test/tesst.go:13) LEAQ type:uintptr(SB), DX
0x00a0 00160 (D:/test/tesst.go:13) MOVQ DX, main..autotmp_2+88(SP)
0x00a5 00165 (D:/test/tesst.go:13) MOVQ AX, main..autotmp_2+96(SP)
0x00aa 00170 (D:/test/tesst.go:13) LEAQ go:string."bytes is %p"(SB), AX
0x00b1 00177 (D:/test/tesst.go:13) MOVL $11, BX
0x00b6 00182 (D:/test/tesst.go:13) LEAQ main..autotmp_2+88(SP), CX
0x00bb 00187 (D:/test/tesst.go:13) MOVL $1, DI
0x00c0 00192 (D:/test/tesst.go:13) MOVQ DI, SI
0x00c3 00195 (D:/test/tesst.go:13) PCDATA $1, $0
0x00c3 00195 (D:/test/tesst.go:13) CALL fmt.Printf(SB)
其中runtime.convT64操作为申请内存操作。