设为首页 加入收藏

TOP

Go语言数组和切片的原理(四)
2019-03-26 16:13:29 】 浏览:379
Tags:语言 切片 原理
:= vauto[:]
  1. 根据切片中的元素数量对底层数组的大小进行推断并创建一个数组;
  2. 将这些字面量元素存储到初始化的数组中;
  3. 创建一个同样指向 [3]int 类型的数组指针;
  4. 将静态存储区的数组 vstat 赋值给 vauto 指针所在的地址;
  5. 通过 [:] 操作获取一个底层使用 vauto 的切片;

[:] 以及类似的操作 [:10] 其实都会在 SSA 代码生成 阶段被转换成 OpSliceMake 操作,这个操作会接受四个参数创建一个新的切片,切片元素类型、数组指针、切片大小和容量。

关键字

如果使用字面量的方式创建切片,大部分的工作就都会在编译期间完成,但是当我们使用 make 关键字创建切片时,在 类型检查 期间会检查 make『函数』的参数,调用方必须传入一个切片的大小以及可选的容量:

func typecheck1(n *Node, top int) (res *Node) {
    switch n.Op {
    // ...
    case OMAKE:
        args := n.List.Slice()

        i := 1
        switch t.Etype {
        case TSLICE:
            if i >= len(args) {
                yyerror("missing len argument to make(%v)", t)
                return n
            }

            l = args[i]
            i++
            var r *Node
            if i < len(args) {
                r = args[i]
            }

            // ...
            if Isconst(l, CTINT) && r != nil && Isconst(r, CTINT) && l.Val().U.(*Mpint).Cmp(r.Val().U.(*Mpint)) > 0 {
                yyerror("len larger than cap in make(%v)", t)
                return n
            }

            n.Left = l
            n.Right = r
            n.Op = OMAKESLICE
        }
    // ...
    }
}

make 参数的检查都是在 typecheck1 函数中完成的,它不仅会检查 len,而且会保证传入的容量 cap 一定大于或者等于 len;随后的中间代码生成阶段会把这里的 OMAKESLICE 类型的操作都转换成如下所示的函数调用:

makeslice(type, len, cap)

当切片的容量和大小不能使用 int 来表示时,就会实现 makeslice64 处理容量和大小更大的切片,无论是 makeslice 还是 makeslice64,这两个方法都是在结构逃逸到堆上初始化时才需要调用的,如果当前的切片不会发生逃逸并且切片非常小的时候,make([]int, 3, 4) 才会被转换成如下所示的代码:

var arr [4]int
n := arr[:3]

在这时,数组的初始化和 [:3] 操作就都会在编译阶段完成大部分的工作,前者会在静态存储区被创建,后者会被转换成 OpSliceMake 操作。

接下来,我们回到用于创建切片的 makeslice 函数,这个函数的实现其实非常简单:

func makeslice(et *_type, len, cap int) unsafe.Pointer {
    mem, overflow := math.MulUintptr(et.size, uintptr(cap))
    if overflow || mem > maxAlloc || len < 0 || len > cap {
        mem, overflow := math.MulUintptr(et.size, uintptr(len))
        if overflow || mem > maxAlloc || len < 0 {
            panicmakeslicelen()
        }
        panicmakeslicecap()
    }

    return mallocgc(mem, et, true)
}

上述代码的主要工作就是用切片中元素大小和切片容量相乘计算出切片占用的内存空间,如果内存空间的大小发生了溢出、申请的内存大于最大可分配的内存、传入的长度小于 0 或者长度大于容量,那么就会直接报错,当然大多数的错误都会在编译期间就检查出来,mallocgc 就是用于申请内存的函数,这个函数的实现还是比较复杂,如果遇到了比较小的对象会直接初始化在 Golang 调度器里面的 P 结构中,而大于 32KB 的一些对象会在堆上初始化。

初始化后会返回指向这片内存空间的指针,在之前版本的 Go 语言中,指针会和长度与容量一起被合成一个 slice 结构返回到 makeslice 的调用方,但是从 020a18c5 这个 commit 开始,构建结构体 SliceHeader的工作就都由上层在类型检查期间完成了:

func typecheck1(n *Node, top int) (res *Node) {
    switch n.Op {
    // ...
    case OSLICEHEADER:
    switch 
        t := n.Type
        n.Left = typecheck(n.Left, ctxExpr)
        l := typecheck(n.List.First(), ctxExpr)
        c := typecheck(n.List.Second(), ctxExpr)
        l = defaultlit(l, types.Types[TINT])
        c = defaultlit(c, types.Types[TINT])

        n.List.SetFirst(l)
        n.List.SetSecond(c)
    // ...
    }
}

OSLICEHEADER 操作会创建一个如下所示的结构体,其中包含数组指针、切片长度和容量,它是切片在运行时的表示:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

正是因为大多数对切片类型的操作并不需要直接操作原 slice 结构体,所以 SliceHeader 的引入能够减少切片初始化时的开销,这个改动能够减少 0.2% 的 Go 语言包大小并且能够减少 92 个 panicindex 的调用。

访问

对切片常见的操作就是获取它的长度或者容量,这两个不同的函数 lencap 其实被 Go 语言的编译器看成是两种特殊的操作 OLENOCAP,它们会在 SSA 生成阶段 被转换成 OpSliceLenOpSliceCap 操作:

func (s *state) expr(n *Node) *ssa.Value {
    switch n.Op {
    case OLEN, OCAP:
        switch {
        case n.Left.Type.IsSlice():
            op := ssa.OpSliceLen
            if n.Op == OCAP {
                op = ssa.OpSliceCap
            }
            return s.newValue1(op, types.Types[TINT], s.expr(n.Left))
        // ...
        }
    // ...
    }
}

除了获取切片的长度和容量之外,访问切片中元素使用的 OINDEX 操作也都在 SSA 中间代码生成期间就转换成对地址的获取操作:

func (s *state) expr(n *Node) *ssa.Value {
    switch n.Op {
    case OINDEX:
        switch {
        case n.Left.Type.IsSlice():
            p := s.addr(n, false)
            return s.load(n.Left.Type.Elem(), p)
        // ...
        }
    // ...
首页 上一页 1 2 3 4 5 下一页 尾页 4/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇理解Golang哈希表Map的元素 下一篇Go指南 - 笔记

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目