咱们先来看下slice的构造体
// runtime/slice.gotype slice struct { array unsafe.Pointer // 元素指针 len int // 长度 cap int // 容量}
咱们先看一下创立slice的办法,咱们用go1.11和go1.18做比拟,go1.11返回的是构造体(slice),go1.18返回的是slice外面的array指针,而后在下层调用方再创立reflect.SliceHeader.这一扭转是在cmd/compile: move slice construction to callers of makeslice
go1.11
// runtime/slice.gofunc makeslice(et *_type, len, cap int) slice { // NOTE: The len > maxElements check here is not strictly necessary, // but it produces a 'len out of range' error instead of a 'cap out of range' error // when someone does make([]T, bignumber). 'cap out of range' is true too, // but since the cap is only being supplied implicitly, saying len is clearer. // See issue 4085. maxElements := maxSliceCap(et.size) if len < 0 || uintptr(len) > maxElements { panicmakeslicelen() } if cap < len || uintptr(cap) > maxElements { panicmakeslicecap() } p := mallocgc(et.size*uintptr(cap), et, true) return slice{p, len, cap}}// cmd/compile/internal/gc/walk.go // n escapes; set up a call to makeslice. // When len and cap can fit into int, use makeslice instead of // makeslice64, which is faster and shorter on 32 bit platforms. if t.Elem().NotInHeap() { yyerror("%v is go:notinheap; heap allocation disallowed", t.Elem()) } len, cap := l, r fnname := "makeslice64" argtype := types.Types[TINT64] // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. // The case of len or cap overflow when converting TUINT or TUINTPTR to TINT // will be handled by the negative range checks in makeslice during runtime. if (len.Type.IsKind(TIDEAL) || maxintval[len.Type.Etype].Cmp(maxintval[TUINT]) <= 0) && (cap.Type.IsKind(TIDEAL) || maxintval[cap.Type.Etype].Cmp(maxintval[TUINT]) <= 0) { fnname = "makeslice" argtype = types.Types[TINT] } fn := syslook(fnname) fn = substArgTypes(fn, t.Elem()) // any-1 n = mkcall1(fn, t, init, typename(t.Elem()), conv(len, argtype), conv(cap, argtype))
go1.18
// runtime/slice.gofunc makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber). // 'cap out of range' is true too, but since the cap is only being // supplied implicitly, saying len is clearer. // See golang.org/issue/4085. mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } return mallocgc(mem, et, true)}// cmd/compile/internal/walk/builtin.go// walkMakeSlice walks an OMAKESLICE node.func walkMakeSlice(n *ir.MakeExpr, init *ir.Nodes) ir.Node { l := n.Len r := n.Cap if r == nil { r = safeExpr(l, init) l = r } t := n.Type() if t.Elem().NotInHeap() { //.... } // n escapes; set up a call to makeslice. // When len and cap can fit into int, use makeslice instead of // makeslice64, which is faster and shorter on 32 bit platforms. len, cap := l, r fnname := "makeslice64" argtype := types.Types[types.TINT64] // Type checking guarantees that TIDEAL len/cap are positive and fit in an int. // The case of len or cap overflow when converting TUINT or TUINTPTR to TINT // will be handled by the negative range checks in makeslice during runtime. if (len.Type().IsKind(types.TIDEAL) || len.Type().Size() <= types.Types[types.TUINT].Size()) && (cap.Type().IsKind(types.TIDEAL) || cap.Type().Size() <= types.Types[types.TUINT].Size()) { fnname = "makeslice" argtype = types.Types[types.TINT] } fn := typecheck.LookupRuntime(fnname) ptr := mkcall1(fn, types.Types[types.TUNSAFEPTR], init, reflectdata.TypePtr(t.Elem()), typecheck.Conv(len, argtype), typecheck.Conv(cap, argtype)) ptr.MarkNonNil() len = typecheck.Conv(len, types.Types[types.TINT]) cap = typecheck.Conv(cap, types.Types[types.TINT]) // 比照go1.11生成了数组指针后再初始化了len和cap // ir.NewSliceHeaderExpr次要就是生成了SliceHeader,参考上面的tcSliceHeader sh := ir.NewSliceHeaderExpr(base.Pos, t, ptr, len, cap) return walkExpr(typecheck.Expr(sh), init)}// cmd/compile/internal/typecheck/expr.go// tcSliceHeader typechecks an OSLICEHEADER node.func tcSliceHeader(n *ir.SliceHeaderExpr) ir.Node { // Errors here are Fatalf instead of Errorf because only the compiler // can construct an OSLICEHEADER node. // Components used in OSLICEHEADER that are supplied by parsed source code // have already been typechecked in e.g. OMAKESLICE earlier. t := n.Type() if t == nil { base.Fatalf("no type specified for OSLICEHEADER") } if !t.IsSlice() { base.Fatalf("invalid type %v for OSLICEHEADER", n.Type()) } if n.Ptr == nil || n.Ptr.Type() == nil || !n.Ptr.Type().IsUnsafePtr() { base.Fatalf("need unsafe.Pointer for OSLICEHEADER") } n.Ptr = Expr(n.Ptr) n.Len = DefaultLit(Expr(n.Len), types.Types[types.TINT]) n.Cap = DefaultLit(Expr(n.Cap), types.Types[types.TINT]) ... return n}// SliceHeader is the runtime representation of a slice.// SliceHeader就是slice的运行时示意// ...type SliceHeader struct { Data uintptr Len int Cap int
所以咱们能够简略的了解成slice的创立返回的是slice构造体,而不是指针.所以在函数参数传递拷贝的时候拷贝的是slice
构造体,而不是*slice
,然而map不一样,map创立的时候是*hmap
,创立的是一个指针.
// runtime/map.go// makemap implements Go map creation for make(map[k]v, hint).// If the compiler has determined that the map or the first bucket// can be created on the stack, h and/or bucket may be non-nil.// If h != nil, the map can be created directly in h.// If h.buckets != nil, bucket pointed to can be used as the first bucket.func makemap(t *maptype, hint int, h *hmap) *hmap { ... return h}
然而咱们很好奇,这里有两个问题:
1.并且%p能输入地址
2.为什么复制slice的时候会影响旧的值
问题1:
其实fmt.Printf()的%p并不只是指针地址,当为slice的时候是输入的是底层数组第0个元素的地址
//go1.18 fmt/print.gofunc (p *pp) fmtPointer(value reflect.Value, verb rune) { var u uintptr switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Slice, reflect.UnsafePointer: u = value.Pointer() default: p.badVerb(verb) return }...}// reflect/value.gofunc (v Value) Pointer() uintptr { k := v.kind() switch k { ... case Slice: //这里的slice返回的指针其实是`*SliceHeader.Data`,Data就是后面slice.array指针 return (*SliceHeader)(v.ptr).Data } panic(&ValueError{"reflect.Value.Pointer", v.kind()})}type SliceHeader struct { Data uintptr Len int Cap int}
问题2:
这是因为复制的时候并不是深拷贝,相当于slice.array还没有变,然而如果咱们append足够大就会申请新的slice.array,如下
func main() { var oldSlice = []int64{1, 2, 3, 4, 5} // len:5,capacity:5 var newSlice = oldSlice[1:3] // len:2,capacity:4 (曾经应用了两个地位,所以还空两地位能够append) fmt.Printf("%p\n", oldSlice) //0xc420098000 fmt.Printf("%p\n", newSlice) //0xc420098008 能够看到newSlice的地址指向的是array[1]的地址,即他们底层应用的还是一个数组 fmt.Printf("%v\n", oldSlice) //[1 2 3 4 5] fmt.Printf("%v\n", newSlice) //[2 3] newSlice[1] = 9 //更改后oldSlice、newSlice都扭转了 fmt.Printf("%v\n", oldSlice) // [1 2 9 4 5] fmt.Printf("%v\n", newSlice) // [2 9] newSlice = append(newSlice, 11, 12) //append 操作之后,oldSlice的len和capacity不变,newSlice的len变为4,capacity:4。因为这是对newSlice的操作 fmt.Printf("%v\n", oldSlice) //[1 2 9 11 12] //留神对newSlice做append操作之后,oldSlice[3],oldSlice[4]的值也产生了扭转 fmt.Printf("%v\n", newSlice) //[2 9 11 12] newSlice = append(newSlice, 13, 14) // 因为newSlice的len曾经等于capacity,所以再次append就会超过capacity值, // 此时,append函数外部会创立一个新的底层数组(是一个扩容过的数组),并将oldSlice指向的底层数组拷贝过来,而后在追加新的值。 fmt.Printf("%p\n", oldSlice) //0xc420098000 fmt.Printf("%p\n", newSlice) //0xc4200a0000 fmt.Printf("%v\n", oldSlice) //[1 2 9 11 12] fmt.Printf("%v\n", newSlice) //[2 9 11 12 13 14] 它俩曾经不再是指向同一个底层数组了}
参考:
- https://draveness.me/golang/d...
- https://pkg.go.dev/fmt