美文网首页人到半百
没看懂的地方-golang-slice

没看懂的地方-golang-slice

作者: HuJay | 来源:发表于2018-02-08 20:39 被阅读0次

    这部分:

    有时候可能需要使用一些比较tricky的技巧,比如利用make弄一块内存自己管理,或者用cgo之类的方式得到的内存,转换为Go类型使用。

    从slice中得到一块内存地址是很容易的:

    s := make([]byte, 200)

    ptr := unsafe.Pointer(&s[0])

    从一个内存指针构造出Go语言的slice结构相对麻烦一些,比如其中一种方式:

    var ptr unsafe.Pointer

    s := ((*[1<<10]byte)(ptr))[:200]

    先将ptr强制类型转换为另一种指针,一个指向[1<<10]byte数组的指针,这里数组大小其实是假的。然后用slice操作取出这个数组的前200个,于是s就是一个200个元素的slice。

    .....................(*[1<<10]byte)................妈蛋

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    2.2 slice

    一个slice是一个数组某个部分的引用。在内存中,它是一个包含3个域的结构体:指向slice中第一个元素的指针,slice的长度,以及slice的容量。长度是下标操作的上界,如x[i]中i必须小于长度。容量是分割操作的上界,如x[i:j]中j不能大于容量。

    数组的slice并不会实际复制一份数据,它只是创建一个新的数据结构,包含了另外的一个指针,一个长度和一个容量数据。 如同分割一个字符串,分割数组也不涉及复制操作:它只是新建了一个结构来放置一个不同的指针,长度和容量。在例子中,对[]int{2,3,5,7,11}求值操作会创建一个包含五个值的数组,并设置x的属性来描述这个数组。分割表达式x[1:3]并不分配更多的数据:它只是写了一个新的slice结构的属性来引用相同的存储数据。在例子中,长度为2--只有y[0]和y[1]是有效的索引,但是容量为4--y[0:4]是一个有效的分割表达式。

    由于slice是不同于指针的多字长结构,分割操作并不需要分配内存,甚至没有通常被保存在堆中的slice头部。这种表示方法使slice操作和在C中传递指针、长度对一样廉价。Go语言最初使用一个指向以上结构的指针来表示slice,但是这样做意味着每个slice操作都会分配一块新的内存对象。即使使用了快速的分配器,还是给垃圾收集器制造了很多没有必要的工作。移除间接引用及分配操作可以让slice足够廉价,以避免传递显式索引。

    slice的扩容

    其实slice在Go的运行时库中就是一个C语言动态数组的实现,在$GOROOT/src/pkg/runtime/runtime.h中可以看到它的定义:

    struct Slice

    { // must not move anything

    byte* array; // actual data

    uintgo len; // number of elements

    uintgo cap; // allocated number of elements

    };

    在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:

    如果新的大小是当前大小2倍以上,则大小增长为新大小

    否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

    make和new

    Go有两个数据结构创建函数:new和make。两者的区别在学习Go语言的初期是一个常见的混淆点。基本的区别是new(T)返回一个*T,返回的这个指针可以被隐式地消除引用(图中的黑色箭头)。而make(T, args)返回一个普通的T。通常情况下,T内部有一些隐式的指针(图中的灰色箭头)。一句话,new返回一个指向已清零内存的指针,而make返回一个复杂的结构。

    有一种方法可以统一这两种创建方式,但是可能会与C/C++的传统有显著不同:定义make(*T)来返回一个指向新分配的T的指针,这样一来,new(Point)得写成make(*Point)。但这样做实在是和人们期望的分配函数太不一样了,所以Go没有采用这种设计。

    slice与unsafe.Pointer相互转换

    有时候可能需要使用一些比较tricky的技巧,比如利用make弄一块内存自己管理,或者用cgo之类的方式得到的内存,转换为Go类型使用。

    从slice中得到一块内存地址是很容易的:

    s := make([]byte, 200)

    ptr := unsafe.Pointer(&s[0])

    从一个内存指针构造出Go语言的slice结构相对麻烦一些,比如其中一种方式:

    var ptr unsafe.Pointer

    s := ((*[1<<10]byte)(ptr))[:200]

    先将ptr强制类型转换为另一种指针,一个指向[1<<10]byte数组的指针,这里数组大小其实是假的。然后用slice操作取出这个数组的前200个,于是s就是一个200个元素的slice。

    或者这种方式:

    var ptr unsafe.Pointer

    var s1 = struct {

    addr uintptr

    len int

    cap int

    }{ptr, length, length}

    s := *(*[]byte)(unsafe.Pointer(&s1))

    把slice的底层结构写出来,将addr,len,cap等字段写进去,将这个结构体赋给s。相比上一种写法,这种更好的地方在于cap更加自然,虽然上面写法中实际上1<<10就是cap。

    或者使用reflect.SliceHeader的方式来构造slice,比较推荐这种做法:

    var o []byte

    sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&o)))

    sliceHeader.Cap = length

    sliceHeader.Len = length

    sliceHeader.Data = uintptr(ptr)

    相关文章

      网友评论

        本文标题:没看懂的地方-golang-slice

        本文链接:https://www.haomeiwen.com/subject/iqhxtftx.html