概述 在做316. Remove Duplicate Letters 遇到了[]byte转string的问题,
发现可以通过unsafe.Pointer
来完成转换,由此产生了对golang中指针操作的兴趣。
[]byte转string 首先不通过指针直接转:
1 2 3 4 5 func main () { var b []byte = []byte {'a' , 'b' , 'c' } var str string = string (b) fmt.Println(str) }
abc
可以看到直接转是可以的,但是直接转整个[]byte会被复制 一遍再给到string中,不是最高效率的做法。
通过指针来转:
注意如果直接转指针,go语言是不允许的:
1 2 var b []byte = []byte {'a' , 'b' , 'c' }var str *string = (*string )(&b)
Cannot convert expression of type []byte to type string
要想进行指针转换,需要使用unsafe.Pointer来中继:
1 2 3 4 5 func main () { var b []byte = []byte {'a' , 'b' , 'c' } var str string = *(*string )(unsafe.Pointer(&b)) fmt.Println(str) }
abc
上面的代码先将[]byte的指针转化为unsafe包中的ArbitraryType类型,然后就可以转化为任意别的类型的指针,
这里就将它转化为*string
类型的指针。
因为string和[]byte的底层的c语言结构为:
1 2 3 4 5 6 7 8 9 10 11 12 struct String { byte* str; intgo len; }; struct Slice { byte* array ; uintgo len; uintgo cap; };
所以可以直接从切片[]byte转到string也毫无违和感。
这样string与切片[]byte共用一片内存 ,本来go的string是不可变的,这里竟然 可以使得string内容可变:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { var b []byte = make ([]byte , 3 , 10 ) b[0 ] = 'a' b[1 ] = 'b' b[2 ] = 'c' var str *string = (*string )(unsafe.Pointer(&b)) fmt.Println(*str) var bp *int = (*int )(unsafe.Pointer(uintptr (unsafe.Pointer(&b)) + unsafe.Sizeof(0 ))) *bp = 4 b[3 ] = 'd' fmt.Println(*str) }
abc abcd
string转[]byte 同理可以直接转,也可以通过指针转:
1 2 3 4 5 6 7 8 func main () { var str string = "abc" var b []byte = ([]byte )(str) fmt.Println(b) var buf []byte = *(*[]byte )(unsafe.Pointer(&str)) fmt.Println(buf) }
[97 98 99] [97 98 99]
注意到通过指针转化得到的[]byte切片是不能进行值得修的,因为str是不可修改的。
这里的buf的cap值是不确定的,因为string结构中只有两个属性,没有cap,有一种骚操作 来转换:
1 2 3 4 5 6 7 func main () { var str string = "abc" xx := *(*[2 ]uintptr )(unsafe.Pointer(&str)) var buf []byte = *(*[]byte )(unsafe.Pointer(&[3 ]uintptr {xx[0 ], xx[1 ], xx[1 ]})) fmt.Println(buf) fmt.Printf("%p %d %d\n" , buf, len (buf), cap (buf)) }
[97 98 99] 0x4c66e4 3 3
这里把string当作一个[2]uintptr
,通过它新建一个[3]uintptr
,这样就得到了一个len和cap都为3的[]byte切片。
struct赋值 例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type V struct { i int32 j int64 } func main () { var v *V = new (V) var i *int32 = (*int32 )(unsafe.Pointer(v)) *i = 1 var j *int64 = (*int64 )(unsafe.Pointer(uintptr (unsafe.Pointer(v)) + unsafe.Offsetof(v.j))) *j = 2 fmt.Println(*v) }
{1 2}
需要注意到go的struct与c语言一样有对齐机制 ,对于结构体V,它明显是按照int64
也就是8字节对齐,
所以上面的代码取j的位置换一种写法也是一样的:
1 var j *int64 = (*int64 )(unsafe.Pointer(uintptr (unsafe.Pointer(v)) + uintptr (8 )))
另外struct使用的是一块连续内存,可以看到下面的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type V struct { i int32 j int64 } func main () { var v *V = new (V) var i *int32 = (*int32 )(unsafe.Pointer(v)) *i = 1 var k *int64 = (*int64 )(unsafe.Pointer(uintptr (unsafe.Pointer(v)) + uintptr (4 ))) *k = 3 << 32 fmt.Println(*v) }
{1 3}
这里的 i,j,k 在内存中的位置是这样的:
所以*k = 3 << 32
,这句话相当于把第9个字节变成了1100 0000
,需要注意到这里是小端序 (数据的低字节保存在内存的低地址)。
所以j的值就变成了3。