在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
概述这篇文章也是长篇,为了把问题记录清楚,还是按照一直坚持的写作习惯,从零开始 int main(int argc, char* argv[]) { char *msg="hello c++"; msg++; printf("%c\n",*msg); //'e' } 【分析】 ”hello c++”字符串在编译之后分配成一个常量形式,也就是一个字面量常量值。msg赋值为该字符串的首地址,假如为0x1000。接着将msg自增,则msg=0x1001,然后打印该地址起始1字节的字符’e’ func main() { s:="hello golang" p:=&s p++ //错误,p非数值类型 } 【分析】 p为字符串s的地址,然后企图执行p++结果编译都通不过,go做不到或者说语言禁止这么做。错误显示,++必须搭配数值类型,但从代码中可以看到p的类型是*string 指针类型,明确说明不允许指针参与加减运算。 unsafe.Pointer怎么解决这个问题呢?系统unsafe包提供一个类型Pointer,可以看到它的定义是: 到这里基本看出来了,unsafe.Pointer实际是一个*int的包装。在go的学习中知道,int是和平台位数关联的,当前系统一般都是64位,因此int在运行时等价于int64,可以表示任意类型的地址,也即不管类型是什么,地址是相同的8个字节存储 看下这个变化: 接着读下系统包的文档,摘取一段: // Pointer represents a pointer to an arbitrary type. There are four special operations 【翻译下】 Pointer可以表示任意类型的指针,对于type Pointer有四个特别的操作:
因此Pointer允许程序破坏类型系统执行读写任意内存的操作,使用时务必小心....云云 下面是实验代码: func main() { s := "hello golang" unsafe_ptr := unsafe.Pointer(&s) //类型指针 -> unsafe.Pointer pstr := (*string)(unsafe_ptr) // unsafe.Pointer -> 类型指针 fmt.Println(*pstr) //打印字符串 } 代码证实了这一点 关联图如下:
下面分述各个转化的作用 unsafe.Pointer -> 类型pointer前面分析了几个转化,先分析从unsafe.Pointer强转到类型pointer有啥用处? 和C比较,发现golang的字符串形式完全不同。C只是简单的以’\0’做为结束标志,而golang是固定形式。 代码: func main() { var a string = "hello" var ptr_header = (*reflect.StringHeader)(unsafe.Pointer(&a)) fmt.Printf("%x\n", ptr_header.Data) //146c558 fmt.Printf("%d\n", ptr_header.Len) //5 } 【分析】 借用了反射包中的reflect.StringHeader结构体,这个结构体实际就是字符串的结构描述。在这里为了方便直接拿过来用,自定义同样可以。进入文档查看一下内部结构: type StringHeader struct { Data uintptr Len int } struct说明了字符串的结构,Data表示字符串真正的首地址,Len表示字符串长度,打印结果证明了这一点。而在技巧上就使用了unsafe.Pointer -> 类型的转换。也许会认为,直接用下面的代码强转不行吗? var header = (*reflect.StringHeader)(&a) 错误:不能将*string类型强转为*StringHeader!
这个地方体现了unsafe.Pointer的作用,因为根据文档,它可以被转化为任意类型的指针属于万能类型 uintptruintptr的本质是integer类型,可以足够容纳任意类型的地址。虽然它和地址有关系,但是语义是完全不同的。 原因在于,整型和指针是完全不同的语义,就像前面提到的,指针是不能加减的(语言禁止这么操作),但是整型是可以正常加减。所以在执行打印header.Data时,把它解释成整数。 下面做个总结:
可以看出,unsafe.Pointer做为桥梁起到中转作用,但是这个指针不能解释内存,有点类似于C中的void*,万能、抽象又空泛 修改后的代码如下: func mem(ptr uintptr, len int) { //打印内存 for i := 0; i < len; i++ { pointer := unsafe.Pointer(ptr + uintptr(i)) fmt.Printf("%c", *(*byte)(pointer)) } } func main() { var a string = "hello" var header = (*reflect.StringHeader)(unsafe.Pointer(&a)) mem(header.Data, header.Len) } 【分析】 这个代码就是三者之间互相转换的技巧,mem()方法中最终转换得到一个具体类型的地址,通过这个地址解释内存。 代码的作用经过一系列的转换并打印内存,作用在于:只要知道一个类型的结构描述,就可以遍历它的底层内存值,这对于了解底层有用处。按着这个思路可以解释数组、切片、结构体等各种类型的底层 不安全指针的基本原理就写到这,后面可以举一反三做更深入的研究,除此外,unsafe包还有几个方法,平常基本没有用到就暂时略过。 unsafe.Pointer在反射中的应用做了四页的铺垫,通过对不安全指针的了解,下面开始记录反射 前面的分析也涉及到反射使用不安全指针进行操作,下面看下具体: 这里把ty的类型也带上为了看的更清楚,reflect.Type是一个只有方法的interface{}类型。 不管什么值进入TypeOf之后,都被转化为interface。interface是一个万能类型,它具有包裹值的所有信息。正是依赖于这个转化,反射才能得到众多信息。 接口可以容纳其它任意类型值,但是接口自己本身也有一个描述,就是emptyInterface类型。然后得到它的成员变量:typ *rtype,而rtype又实现了Type接口,所以ty最终是reflect.Type类型,可以调用Type的接口方法进行操作。这就是反射的准备工作。 类型的扩展首先对上面的内容做个示意图: 图示说明前面一系列的操作过程,最终结果是:由原始值int 20得到一个接口描述rtype。 在记录string的时候知道,得到一个值的地址只是得到一个类型描述结构的起始处,比对来看 源码如下: 这段源码清楚的表明,rtype再一次被强转成了structType类型,再查看其源码: 发现structType除了有rtype之外,还有pkgPath,fields字段,说明结构类型扩展了,原因是struct需要记录的信息更多 下面看下强转的思路,图示如下: 如图,假如类型T有四个成员,类型A具备其中三个,那么就可以把T强转成类型A,也就是说强转的类型一定是原类型的子集,而且对应的成员类型和顺序完全相同。代码面前无秘密: type T struct { t1 int t2 int t3 string } type A struct { a1 int a2 int } func main() { var t = T{ 1, 2, "t", } var a = (*A)(&t) //不能从*T转化为*A } 【分析】 A想把T中自己能对应的成员搬出来,代码使用(*A)强转,但是根本不通。这里就是unsafe.Pointer施展本领的地方 这个问题同时给出一个非常好的启示:如何从类型T中按类型S截取数据?上述代码给了答案
本篇结束
package main import ( "fmt" "unsafe" ) //基础类型 type _rtype struct { a1 int a2 int } //struct : 扩展类型 type _structType struct { _rtype pkgpath string } type iface struct { rt *_rtype p unsafe.Pointer } func main() { var t = iface{ rt: &_rtype{ 20, 30, }, p: nil, } var s = (*_structType)(unsafe.Pointer(t.rt)) fmt.Println(s.a1) }
|
请发表评论