go字符串无拷贝转换切片的一个问题
go字符串无拷贝转换切片的一个问题
ivansli1.字符串无拷贝转换切片
提起 字符串无拷贝转换切片
这个话题,可能很多人会想到下面一段代码:
1 | func string2Slice(s string) []byte { |
这段代码利用了指针进行强转,并且在实现过程中不会出现数据拷贝,简单、强大!
2.同一切片多次获取容量结果不相同
来看一个测试用例:
1 | package test |
执行结果如下:
1 | === RUN TestString2Slice |
不知道你发现没有:相同切片,内容完全一样,多次获取其容量 cap,得到的结果竟然不一样!
每个人每次执行结果也绝对不一样
是不是觉得自己发现了惊天动地的 bug,搓搓手准备给 go 官方提 issue 了。
3.字符串无拷贝转换切片的原理
在 go 源码 src/reflect/value.go 中有这么两个结构体来分别表示 字符串、切片:
1 | type StringHeader struct { |
我们发现:字符串、切片的底层实现分别是两个不同的结构体,这两个结构体的前两个字段名称、类型竟然也相同。
Data 指向底层存储字符数组的指针
Len 底层字符数组的长度
同时思考:
① 安全的字符串 / 不安全的字符串分别指什么?
② C语言的字符串为什么不安全?
也正是因为结构体的前两个字段名称、类型相同,才使得我们能够投机取巧进行无拷贝的转换。
不知道你思考过强类型编程语言中变量必须指定参数类型的意义没?
在内存中地址空间是连续的,每一个存储数据的空间都有地址编号。在操作数据时经常传递数据的指针,通过指针来获取数据。
数据的起始地址 到 偏移该数据类型大小的结束地址 之间的空间,就是来存储该数据具体值的,通过操作这个区间来操作数据。
在C语言中经常会听到
野指针
这个说法,可以思考一下指是什么
由于这两种类型的底层前两个字段名称、类型相同,可以让字符串 假装(指针操作)
自己就是切片。
设定个场景:
未来技术非常发达,有钱人可以克隆很多个自己。
克隆的人虽拥有主体的外貌,但有一样是他们不具备的:记忆。所以,这些克隆人永远也不是主体 (除非记忆也可以移植)。
虽然可以通过指针让 字符串假装成切片,但是这个切片却不具有切片具有的一个特性:容量 cap
。
如果这个时候,你去问问这个
假装自己是切片的字符串
一个问题:你的容量是多少?假装自己是切片的字符串
由于没有容量这个属性,只能乱猜一个数字。
测试用例中 字符串无拷贝转换切片
多次打印容量值,得到不一样结果的深层次原因就是:越界。访问了自己身后不属于自己的空间内的数值,这个空间内的数值随着程序的运行会发生不确定的变化,所以多次读取到的数值不相同,也可以认为是脏读
。
你可能会问,越界了为什么还不报错?
那是因为,字符串假装(指针操作)
自己就是切片的这种设定把 系统也蒙蔽了,系统也不知道发生了越界
总结
对于 字符串无拷贝转换切片
这个话题,很多人可能只是知道怎么写,却不会考虑到存在的问题。
所以,笔者觉得很多时候,不仅要知其然、也要知其所以然。
在实际开发中,除非有特殊原因,并且知道存在的问题,否则就不要为了酷炫而滥用。不然,bug之路会漫长而无趣。