• 本篇文章都是slice的源码走读。
  • 注意:slice 不是并发安全的数据结构,大家在使用时请务必注意并发安全问题。

type slice struct

  1. 切片的内存布局。
    • array:指向一个[cap]T大小的数组地址。就是指向一个cap容量大小的数组首地址。
    • len:记录切片已存储元素的长度,也是可访问的最大下标len - 1
    • cap:记录切片的容量,也就是当前切片存储的最大元素数量(未扩容前)。
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

type notInHeapSlice struct

  1. notInHeapSlicego:notinheap内存支持的slice
  2. 也就是该类型的对象不是在堆中创建的,也就是GC不会扫描,多用于内存管理模块中。
1
2
3
4
5
6
// A notInHeapSlice is a slice backed by go:notinheap memory.
type notInHeapSlice struct {
    array *notInHeap    // 指向一个起始地址
    len   int
    cap   int
}

type notInHeap struct

  1. notInHeap是由sysAllocpersistentAlloc等底层分配器分配的堆外内存。
  2. 一般来说,最好使用标记为go:notinheap的真实类型,但在无法这样做的情况下(比如在分配器中),它用作通用类型。
  3. TODO:使用它作为sysAlloc,persistentAlloc等的返回类型?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// notInHeap is off-heap memory allocated by a lower-level allocator
// like sysAlloc or persistentAlloc.
//
// In general, it's better to use real types marked as go:notinheap,
// but this serves as a generic type for situations where that isn't
// possible (like in the allocators).
//
// TODO: Use this as the return type of sysAlloc, persistentAlloc, etc?
//
//go:notinheap
type notInHeap struct{}

add()

1
2
3
4
func (p *notInHeap) add(bytes uintptr) *notInHeap {
    // p + bytes
    return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes))
}

make()

  1. make内置函数分配并初始化一个类型为slicemapchan的对象(only)。
  2. new一样,第一个参数是类型,而不是值。与new不同,make的返回值类型与其参数的类型相同,而不是指向参数的指针。
  3. 具体的结果取决于类型:
    1. Slice
      • size指定了长度。切片的容量等于它的长度。
      • 以提供第二个整数参数来指定不同的容量;它必须不小于长度。
      • 例如,make([]int, 0, 10)会分配一个长度为10的底层数组,并返回一个长度为0、容量为10的切片。
    2. Map:一个空的map分配了足够的空间来保存指定数量的元素。在这种情况下,可以省略长度,分配一个较小的起始长度。
    3. Channel:channel缓冲区使用指定的缓冲区容量初始化。如果为0,或者size被省略,则channel是无缓冲的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//	Slice: The size specifies the length. The capacity of the slice is
//	equal to its length. A second integer argument may be provided to
//	specify a different capacity; it must be no smaller than the
//	length. For example, make([]int, 0, 10) allocates an underlying array
//	of size 10 and returns a slice of length 0 and capacity 10 that is
//	backed by this underlying array.
//	Map: An empty map is allocated with enough space to hold the
//	specified number of elements. The size may be omitted, in which case
//	a small starting size is allocated.
//	Channel: The channel's buffer is initialized with the specified
//	buffer capacity. If zero, or the size is omitted, the channel is
//	unbuffered.
func make(t Type, size ...IntegerType) Type

makeslice()

  1. make([]T *_type, len, cap int)
    • *_type:记录着切片元素类型,比如[]string切片这里是string的元类型。
    • len:切片的长度,该参数是必传。
    • cap:切片的容量,该参数是可传,默认会传len大小。
  2. makeslice()函数是切片申请内存的make()函数原型,主要负责申请slice.array字段指向的内存大小。
  3. 那么切片的24字节大小内存是在什么时候分配的?(64位系统下为24字节内存,32系统下为12字节内存)
    • 可能在函数栈上直接分配24字节大小内存。
    • 也可能在堆上分配24字节大小内存。
  4. 调用了makeslice()函数,其slice.array指向的内存块一定是在上。没有调该函数时可能内存分配在上。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func makeslice(et *_type, len, cap int) unsafe.Pointer {
    // 1)判断et.size * uintptr(cap)是否造成内存溢出
    mem, overflow := math.MulUintptr(et.size, uintptr(cap)) // 按照cap计算的
    //  1. 【overflow == true】:溢出
    //  2. 【mem > maxAlloc】:超过操作系统最大内存
    //  3. 【len < 0】:错误的len参数
    //  4. 【len > 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.
        // 
        // 当有人 make([]T, bignumber) 时,产生一个 'len out of range' 错误而不是 'cap out of range' 错误提示
        // 当 'cap out of range' 也是太长了,由于cap只是隐式地提供,所以说len更清楚的提示。
        mem, overflow := math.MulUintptr(et.size, uintptr(len))	// 根据len计算
        if overflow || mem > maxAlloc || len < 0 {
            panicmakeslicelen() // painc 'len out of range'
        }
        panicmakeslicecap() // panic 'cap out of range'
    }

    // 2)向操作系统申请mem大小的内存块,返回申请到内存块的首地址
    return mallocgc(mem, et, true)
}

makeslice64()

  1. int64版本,如果当前是在32位系统中时,int其实是int32大小。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer {
    len := int(len64) // 32位系统下转换会丢失部分数据
    if int64(len) != len64 {
        panicmakeslicelen() // painc 'len out of range'
    }

    cap := int(cap64)
    if int64(cap) != cap64 {
        panicmakeslicecap() // panic 'cap out of range'
    }

    return makeslice(et, len, cap)
}

len()

  1. 以下伪代码获取切片的长度。
  2. len函数的原型:func len(array []T) int
1
2
3
4
// 伪代码示例
func len(array []T) int {
    return array.len   
}

cap()

  1. 以下伪代码获取切片的容量。
  2. cap函数的原型:func cap(array []T) int
1
2
3
4
// 伪代码示例
func cap(array []T) int {
    return array.cap
}

copy()

  1. copy内置函数将元素从源片复制到目标片。(作为特殊情况,它也会将bytes从string复制到byte切片。)
  2. 源和目标可能重叠。
  3. copy返回复制的元素数量,这将是len(src)len(dst)的最小值。src->dst
1
2
3
4
5
6
// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int

slicecopy()

  1. slicecopy用于将pointerless元素的字符串或切片复制到切片中。
  2. 注意:copy()的函数原型中没有可变参数(... T)的形式参数。
  3. slicecopy 适用以下两种情况:fm -> to
    • copy(to, fm []T) int
    • copy(to []byte, fm string) int
  4. 注意以下slicecopy函数可能在go1.18+版本中不是这样的,这一版采用的是go1.22左右版本的源码,但是只是发生了变化具体逻辑没变。
  5. 参数:fromPtr -> toPtr
    • toPtr unsafe.Pointer:目标地址,也就是上面的to.array值。
    • toLen int:目标长度,也就是上面的to.len值。
    • fromPtr unsafe.Pointer:来源地址,也就是上面的fm.array值。
    • fromLen int:来源长度,也就是上面的fm.len值。
    • width uintptr:切片类型占用内存大小,也就是[]TT类型的大小。
  6. 返回值:
    • int:拷贝的元素数量。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// slicecopy is used to copy from a string or slice of pointerless elements into a slice.
func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {
    // 1) 拷贝或被拷贝长度为0直接返回
    if fromLen == 0 || toLen == 0 {
        return 0
    }

    // 2) 拷贝元素的个数取决于拷贝或被拷贝的最小长度
    n := fromLen
    if toLen < n {
        n = toLen
    }

    // 3) 拷贝的元素大小为0,直接返回n
    if width == 0 {	// []struct{}
        return n
    }

    // 4) size 需要拷贝的总内存大小/字节。
    size := uintptr(n) * width
    
    if raceenabled {
        callerpc := getcallerpc()
        pc := abi.FuncPCABIInternal(slicecopy)
        racereadrangepc(fromPtr, size, callerpc, pc)
        racewriterangepc(toPtr, size, callerpc, pc)
    }
    if msanenabled {
        msanread(fromPtr, size)
        msanwrite(toPtr, size)
    }
    if asanenabled {
        asanread(fromPtr, size)
        asanwrite(toPtr, size)
    }

    // 一般情况下,这里的值大约是2x(只有1字节需要拷贝)
    // 	to := make([]byte, 1); copy(to, "hello")
    // 【[]byte】 OR 【[]uint8】 OR 【[]int8】 OR 【[]bool】
    if size == 1 { // common case worth about 2x to do here
        // TODO: is this still worth it with new memmove impl?
        // 
        // TODO: 使用新的memmove impl,这仍然值得吗?
        // 已知fromPtr和toPtr是 byte 指针
        *(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer
    } else {
        memmove(toPtr, fromPtr, size)   // 拷贝数据
    }
    return n
}

memmove()

  1. memmove从from复制n个字节到to
  2. memmove确保任何位于from中的指针都以不可分割的写入方式写入到to中,因此,动态读取无法观察到一个只写了一半的指针。
  3. 这是必要的,以防止垃圾收集器发现无效指针,这与非托管语言中的memmove不同。
  4. 不过,只有当fromto可能包含指针时,memmove()才需要这么做,只有当from、to和n都是 word-aligned时才会这样做。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// memmove copies n bytes from "from" to "to".
//
// memmove ensures that any pointer in "from" is written to "to" with
// an indivisible write, so that racy reads cannot observe a
// half-written pointer. This is necessary to prevent the garbage
// collector from observing invalid pointers, and differs from memmove
// in unmanaged languages. However, memmove is only required to do
// this if "from" and "to" may contain pointers, which can only be the
// case if "from", "to", and "n" are all be word-aligned.
//
// Implementations are in memmove_*.s.
//
//go:noescape
func memmove(to, from unsafe.Pointer, n uintptr)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
TEXT runtime·memmove<ABIInternal>(SB), NOSPLIT, $0-24
    // AX = to
    // BX = from
    // CX = n
    MOVQ    AX, DI
    MOVQ    BX, SI
    MOVQ    CX, BX
    
tail:
    // TEST指令用于对BX寄存器的内容和自身进行按位与操作,但是不改变寄存器的内容。
    TESTQ   BX, BX # 检查BX是否为0
    // 检查上一条指令(TEST)执行后是否设置了零标志(ZF)
    JEQ move_0  # Jump if Equal
    // 这是一个比较指令,用于比较BX寄存器中的值和立即数2。
    // 具体来说,它会将BX寄存器的值和2相减,但不改变任何寄存器的值,只是根据结果设置状态标志。
    CMPQ    BX, $2
    // BX <= 2 成立
    JBE move_1or2 # Jump if Below or Equal
    CMPQ    BX, $4 # <= 4
    JB  move_3
    JBE move_4
    CMPQ    BX, $8 # <= 8
    JB  move_5through7
    JE  move_8
    CMPQ    BX, $16
    JBE move_9through16
    CMPQ    BX, $32
    JBE move_17through32
    CMPQ    BX, $64
    JBE move_33through64
    CMPQ    BX, $128
    JBE move_65through128
    CMPQ    BX, $256
    JBE move_129through256

    TESTB   $1, runtime·useAVXmemmove(SB)
    JNZ avxUnaligned
    
    //... ...

append()

  1. 内置函数append()将元素添加到切片的末尾。
  2. 如果它有足够的容量,目的地将被重新划分以容纳新的元素。如果没有,将分配一个新的底层数组。
  3. 注意:append()函数存在可变参数(... T)的形式的参数。
  4. append()返回更新后的slice。因此,有必要将append()的结果存储在保存切片本身的变量中:
    • slice = append(slice, elem1, elem2)
    • slice = append(slice, anotherSlice...)
  5. 作为一种特殊情况,可以将字符串添加到字节切片中,如下所示:
    • slice = append([]byte("hello "), "world"...)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//
//	slice = append(slice, elem1, elem2)
//	slice = append(slice, anotherSlice...)
//
// As a special case, it is legal to append a string to a byte slice, like this:
//
//	slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
  1. append 执行步骤:
    • 如果当前append()函数执行完后切片不会“翻倍扩容"那么,直接是把append()后追加的数据拷贝到切片的后续空间即可。
    • 如果当前append()函数执行完后需要“翻倍扩容”,那么先调用runtime.growslice()扩容函数,然后在拷贝数据追加到新的内存空间。

growslice()

  1. growslice()append()函数期间处理切片增长。
  2. 它将slice元素类型、旧的slice和所需的新最小容量传递给它,然后返回一个至少具有该容量的新slice,并将旧数据复制到其中。
  3. 新slice的长度被设置为旧slice的长度,而不是新请求的容量。
  4. 这是为了方便codegen。旧片的长度立即用于计算在追加期间在何处写入新值。
  5. TODO:当旧的后端消失时,重新考虑这个决定。
  6. SSA后端可能更喜欢新的长度,或者只返回ptr/cap以节省栈空间。
  7. 参数:
    • et *_type:切片元素的元类型。
    • old slice:未翻倍扩容前切片。
    • cap intappend()函数后需要的长度 old.len + n = cap。也就是append(s S, x ...T) S函数中len(S) + len(x) = cap后的长度。
  8. 返回值:slice
    • slice.data:新申请的地址。
    • slice.lenold.len的值。注意这里是旧切片的长度。
    • slice.cap:扩容后的容量。
  9. 该函数在append()函数调用时根据条件触发。如old = append(old, 1, 2, 1), len(old) + 3 > cap(old)时就需要扩容了。
  10. 注意,关于slice的扩容规则在go1.18前是根据len的一套规则,而在以后版本又是根据cap的一套规则,本篇采用的是go1.22左右版本的。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice {
    if raceenabled {
        callerpc := getcallerpc()
        racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice))
    }
    if msanenabled {
        msanread(old.array, uintptr(old.len*int(et.size)))
    }
    if asanenabled {
        asanread(old.array, uintptr(old.len*int(et.size)))
    }

    // 1) 切片长度溢出判断
    if cap < old.cap {
        panic(errorString("growslice: cap out of range"))
    }

    // 2) 切片元素类型 占用内存为零 情况
    // 这种情况出现在:
    //      var s []struct{}
    //      s = append(s, struct{}{}, struct{}{})
    if et.size == 0 {
        // append should not create a slice with nil pointer but non-zero len.
        // We assume that append doesn't need to preserve old.array in this case.
        //
        // Append不应该创建一个指针为nil的切片,而是一个len为non-zero的切片。
        // 在这种情况下,我们假设append不需要保存old.array。
        // 赋值slice.array指定地址,为了确保slice不是nil
        // slice为nil的判断条件是,只要slice.array==0x00,不管len和cap的值为多少都为nil
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    // 3) 评估扩容后的容量
    // ---+-------+-----------------------------------------------------------------------------------
    // 预 |   if  | oldCap * 2 < cap ------> newCap = cap               使用cap值
    // 估 |-------+-----------------------------------------------------------------------------------
    // 规 |  else | oldCap < 256     ------> newCap = oldCap * 2        翻倍扩容
    // 则 |       | oldCap >= 256    ------> newCap = oldCap * 5/4 + 256 * 3/4 在原容量上扩容1/4在扩容192
    // ---+-------+-----------------------------------------------------------------------------------
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        // 2倍旧容量 < cap时,则按照cap算。
        newcap = cap
    } else {
        const threshold = 256
        if old.cap < threshold {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            //
            // 检查 0 < newcap 以检测溢出并防止无限循环。
            for 0 < newcap && newcap < cap {
                // Transition from growing 2x for small slices
                // to growing 1.25x for large slices. This formula
                // gives a smooth-ish transition between the two.
                newcap += (newcap + 3*threshold) / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    // 4) 内存规格匹配
    
    // 内存是否溢出 true.溢出 false.没有溢出
    var overflow bool	
    // lenmem 旧切片元素占用的内存大小
    //      该值用于迁移旧数据的依据/字节
    // newlenmem 翻倍后切片元素占用的内存大小
    //      该值是当前扩容后实际占用的大小/字节
    //      因此capmem-newlenmem这部分内存是多余的,不会被用到。
    // capmem 翻倍后新容量占用的内存大小,
    //      用于向操作系统申请的内存大小/字节
    //      这部分内存可能大于newlenmem的值,因为Go的内存申请是有规格的。
    var lenmem, newlenmem, capmem uintptr
    // Specialize for common values of et.size.
    // For 1 we don't need any division/multiplication.
    // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    // For powers of 2, use a variable shift.
    //
    // 专门用于 et.size 的共同值。
    // 对于1,我们不需要任何除法/乘法
    // 对于 goarch.PtrSize,编译器将除法/乘法 优化为一个常量的位移
    // 对于2的幂次方,使用可变位移
    switch {
        // 倘若数组元素的大小为 1,则新容量大小为 1 * newcap.
        // 同时会针对 span class 进行取整
    case et.size == 1:  // 1字节
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))   // 匹配最近接的内存块规格
        overflow = uintptr(newcap) > maxAlloc   // 是否内存溢出
        newcap = int(capmem)                    // 从新调整翻倍后新容量
        // 倘若数组元素为指针类型,则根据指针占用空间结合元素个数计算空间大小
        // 并会针对 span class 进行取整
    case et.size == goarch.PtrSize: // 4或8字节
        lenmem = uintptr(old.len) * goarch.PtrSize
        newlenmem = uintptr(cap) * goarch.PtrSize
        capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
        overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
        newcap = int(capmem / goarch.PtrSize)
        // 倘若元素大小为 2 的指数,则直接通过位运算进行空间大小的计算  
    case isPowerOfTwo(et.size): // 2的幂次方
        var shift uintptr
        if goarch.PtrSize == 8 {
            // Mask shift for better code generation.
            //
            // 掩码移位以更好地生成代码。
            // sys.Ctz64函数计数尾部(低阶)零,如果全部为零,则为64。
            // 比如 et.size 是2^8也就是 1_0000_0000,也就是8个零
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63    // 64位
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31    // 32位
        }
        lenmem = uintptr(old.len) << shift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        overflow = uintptr(newcap) > (maxAlloc >> shift)
        newcap = int(capmem >> shift)
        // 兜底分支:根据元素大小乘以元素个数
        // 再针对 span class 进行取整     
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        // math.MulUintptr 返回 capmem = et.size * uintptr(newcap); overflow 是否溢出
        capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }
    // 以上代码因为会去匹配内存规格,所以会从新计算newcap这个翻倍后的值

    // The check of overflow in addition to capmem > maxAlloc is needed
    // to prevent an overflow which can be used to trigger a segfault
    // on 32bit architectures with this example program:
    //
    // 除了capmem > maxAlloc之外,还需要检查溢出,以防止溢出,
    // 该溢出可用于在32位体系结构上触发段故障,示例程序如下:
    //
    // type T [1<<27 + 1]int64
    //
    // var d T
    // var s []T
    //
    // func main() {
    //   s = append(s, d, d, d, d)	
    //   print(len(s), "\n")
    // }
    // 4*(1<<27 + 1)*8
    if overflow || capmem > maxAlloc {
        panic(errorString("growslice: cap out of range"))
    }

    // 申请到的内存首地址
    var p unsafe.Pointer
    if et.ptrdata == 0 { // 切片元素类型不包含指针
        // capmem 申请的内存; nil 类型元类型用于判断是否为指针类型; false 是否重置内存为零值;
        p = mallocgc(capmem, nil, false) // 向操作系统申请内存块
        // The append() that calls growslice is going to overwrite from 
        // old.len to cap (which will be the new length).
        // Only clear the part that will not be overwritten.
        //
        // 调用 growslice 的 append() 方法会将 old.len 覆盖到 cap(这将是新的长度)。
        // 只清除不会被覆盖的部分。
        // 清零capmem-newlenmem这块内存,这快内存是多余的,不会被用到。
        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)	
    } else { // 切片元素类型包含指针
        // Note: can't use rawmem (which avoids zeroing of memory), 
        // because then GC can scan uninitialized memory.
        // 
        // Note: 不能使用rawmem(它可以避免内存归零),因为这样GC会扫描未初始化的内存。
        p = mallocgc(capmem, et, true) // 向操作系统申请内存块
        if lenmem > 0 && writeBarrier.enabled { // 开启了写屏障
            // Only shade the pointers in old.array since we know the destination slice p
            // only contains nil pointers because it has been cleared during alloc.
            //
            // 在 old.array 中只对指针进行 shade 处理,因为我们知道目标切片 p 只包含nil指针,
            // 因为它在alloc期间已被清除。
            // lenmem-et.size+et.ptrdata 刚好是old.array存在的都是指针
            //      -et.size:减去最后一个元素的内存
            //      +et.ptrdata:再加上最后一个元素的指针
            // 刚好处理完最后一个元素后面不是指针的部分内存。
            // [dst, dst+size]
            bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)	
        }
    }
    // 从old.array中迁移lenmem大小内存数据到p中
    memmove(p, old.array, lenmem)

    // 注意:这里返回的是 old.len,因为此时还是之前的旧数据
    return slice{p, old.len, newcap}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// bulkBarrierPreWriteSrcOnly is like bulkBarrierPreWrite but
// does not execute write barriers for [dst, dst+size).
//
// In addition to the requirements of bulkBarrierPreWrite
// callers need to ensure [dst, dst+size) is zeroed.
//
// This is used for special cases where e.g. dst was just
// created and zeroed with malloc.
//
// The type of the space can be provided purely as an optimization,
// however it is not used with GOEXPERIMENT=noallocheaders.
//
//go:nosplit
func bulkBarrierPreWriteSrcOnly(dst, src, size uintptr, _ *abi.Type) {
    // GC并发标记阶段,这里需要处理混合写屏障相关事项,因为在拷贝指针数据
    if (dst|src|size)&(goarch.PtrSize-1) != 0 {
        throw("bulkBarrierPreWrite: unaligned arguments")
    }
    if !writeBarrier.enabled {
        return // 并发标记已结束
    }
    buf := &getg().m.p.ptr().wbBuf // 写屏障缓冲区
    h := heapBitsForAddr(dst, size)
    for {
        var addr uintptr
        if h, addr = h.next(); addr == 0 {
            break
        }
        srcx := (*uintptr)(unsafe.Pointer(addr - dst + src))
        p := buf.get1()
        p[0] = *srcx
    }
}
  • 所有0字节分配的基地址。
1
2
// base address for all 0-byte allocations
var zerobase uintptr

roundupsize()

  1. mallocgc返回将分配的内存块的大小,如果您要求该大小。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
    // _MaxSmallSize = 32768
    if size < _MaxSmallSize {
        // smallSizeMax = 1024
        if size <= smallSizeMax-8 { // 以最下8B倍数对齐
            // smallSizeDiv = 8,divRoundUp 等价于 ceil(size/8)
            // size_to_class8和class_to_size 记录着 size 的映射关系
            return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
        } else {    // 以最小128B倍数对齐
            // largeSizeDiv = 128
            return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
        }
    }
    // _PageSize = 8192
    if size+_PageSize < size {
        return size
    }
    return alignUp(size, _PageSize) // 对齐8KB
}

alignUp()

  1. alignUp将n取整为a的倍数。a必须是2的幂。
1
2
3
4
// alignUp rounds n up to a multiple of a. a must be a power of 2.
func alignUp(n, a uintptr) uintptr {
    return (n + a - 1) &^ (a - 1)
}

MulUintptr()

  1. MulUintptr返回a * b以及乘法运算是否溢出。
  2. 在受支持的平台上,这是由编译器降低的固有特性。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// MulUintptr returns a * b and whether the multiplication overflowed.
// On supported platforms this is an intrinsic lowered by the compiler.
func MulUintptr(a, b uintptr) (uintptr, bool) {
    // a|b < 1<<16 || a|b < 1<<32
    if a|b < 1<<(4*goarch.PtrSize) || a == 0 {
        return a * b, false
    }
    // const MaxUintptr = ^uintptr(0)
    overflow := b > MaxUintptr/a
    return a * b, overflow
}