The Go language’s Slice works well, though there are some pitfalls. Slice is an important data structure of Go language. So many articles have been written online that it seems unnecessary to write any more. But everyone has a different way of looking at a problem, and so does what they write. This article is going to look at it from the lower level of assembly language. And in the process of writing this article, I found that most of the articles have some problems, which will be mentioned in the article. I will not expand here.

I hope this puts an end to this topic and the next time someone wants to talk to you about Slice, just drop the link to this article.

What exactly are we talking about when we say slice

Slice is similar to an array in that it can be accessed using subscripts, which can cause panic if crossed. But it is more flexible than arrays and can be automatically expanded.

The easiest way to understand the essence of Slice is to look at its source code:

// runtime/slice.go
type slice struct {
	array unsafe.Pointer // Element pointer
	len   int / / the length
	cap   int / / capacity
}
Copy the code

See, slice has three properties: Pointers to the underlying array; Length: indicates the number of available slice elements, that is, when subscripts are used to access slice elements, the subscripts cannot exceed the length of slice. Capacity, the number of elements in the underlying array, capacity >= length. Capacity is also the maximum size slice can expand without expanding the underlying array.

Note that the underlying array can be pointed to by multiple slices at the same time, so operations on elements of one slice can affect other slices.

Slice the creation of

Slice can be created in one of the following ways:

The serial number way Code sample
1 Direct statement var slice []int
2 new slice := *new([]int)
3 literal Slice: int [] = {1, 2, 3, 4, 5}
4 make slice := make([]int, 5, 10)
5 “Intercept” from a slice or array slice := array[1:5]slice := sourceSlice[1:5]

Direct statement

The first created slice is actually a nil slice. It has zero length and zero capacity. Comparison to nil results in true.

What is confusing here is the empty slice, whose length and capacity are also 0, but all the data Pointers of the empty slice point to the same address 0xC42003bda0. Empty slices and nil compare to false.

Their internal structure is shown below:

Create a way Nil slice Empty section
Methods a var s1 []int var s2 = []int{}
Way 2 var s4 = *new([]int) var s3 = make([]int, 0)
The length of the 0 0
capacity 0 0
andnilTo compare true false

Nil slices are very similar to empty slices, with both length and capacity being 0. It is officially recommended to use nil slices as much as possible.

An exploration of nil slice and empty slice can be found in an article titled “In-depth Analysis of Three Special States of” Slice “in Go by Lao Qian, author of the public account” Code Hole “, which is attached to the Resources section.

literal

Easy to create directly with the initialization expression.

package main

import "fmt"

func main(a) {
	s1 := []int{0.1.2.3.8: 100}
	fmt.Println(s1, len(s1), cap(s1))
}
Copy the code

Running results:

[0 1 2 3 00 00 100] 9 9Copy the code

The only notable thing about the code example above is that it uses the index number, which is assigned directly, so that the rest of the unspecified elements default to 0.

make

The make function takes three arguments: slice type, length, and capacity. Of course, the capacity can not pass, default and length equal.

In the previous article, “Getting to the Bottom of Go,” we learned about the assembly tool, but this time we’ll take a closer look at Slice by using assembly again. If you haven’t read the previous article, it’s better to go back and read it before continuing.

Let’s start with a little toy code that uses the make keyword to create slice:

package main

import "fmt"

func main(a) {
	slice := make([]int.5.10) // The length is 5 and the capacity is 10
	slice[2] = 2 // Assign 2 to the element with index 2
	fmt.Println(slice)
}
Copy the code

Execute the following command to get the Go assembly code:

go tool compile -S main.go
Copy the code

Let’s just focus on the main function:

0x0000 00000 (main.go:5)TEXT "".main(SB), $96-0 0x0000 00000 (main.go:5)MOVQ (TLS), CX 0x0009 00009 (main.go:5)CMPQ SP, 16(CX) 0x000d 00013 (main.go:5)JLS 228 0x0013 00019 (main.go:5)SUBQ $96, SP 0x0017 00023 (main.go:5)MOVQ BP, 88(SP) 0x001c 00028 (main.go:5)LEAQ 88(SP), BP 0x0021 00033 (main.go:5)FUNCDATA $0, Gclocals · 69 c1753bd5f81501d95132d08af04464 (SB) 0 x0021 00033 (main) go: 5) FUNCDATA $1, Gclocals, 57 cc5e9a024203768cbab1c731570886 (SB) 0 x0021 00033 (main) go: 5) LEAQ the int (SB), AX 0x0028 00040 (main.go:6)MOVQ AX, (SP) 0x002c 00044 (main.go:6)MOVQ $5, 8(SP) 0x0035 00053 (main.go:6)MOVQ $10, 16(SP) 0x003e 00062 (main.go:6)PCDATA $0, $0 0x003e 00062 (main.go:6)CALL runtime.makeslice(SB) 0x0043 00067 (main.go:6)MOVQ 24(SP), AX 0x0048 00072 (main.go:6)MOVQ 32(SP), CX 0x004d 00077 (main.go:6)MOVQ 40(SP), DX 0x0052 00082 (main.go:7)CMPQ CX, $2 0x0056 00086 (main.go:7)JLS 221 0x005c 00092 (main.go:7)MOVQ $2, 16(AX) 0x0064 00100 (main.go:8)MOVQ AX, "".. autotmp_2+64(SP) 0x0069 00105 (main.go:8)MOVQ CX, "".. autotmp_2+72(SP) 0x006e 00110 (main.go:8)MOVQ DX, "".. autotmp_2+80(SP) 0x0073 00115 (main.go:8)MOVQ $0, "".. autotmp_1+48(SP) 0x007c 00124 (main.go:8)MOVQ $0, "".. autotmp_1+56(SP) 0x0085 00133 (main.go:8)LEAQ type.[]int(SB), AX 0x008c 00140 (main.go:8)MOVQ AX, (SP) 0x0090 00144 (main.go:8)LEAQ "".. autotmp_2+64(SP), AX 0x0095 00149 (main.go:8)MOVQ AX, 8(SP) 0x009a 00154 (main.go:8)PCDATA $0, $1 0x009a 00154 (main.go:8)CALL runtime.convT2Eslice(SB) 0x009f 00159 (main.go:8)MOVQ 16(SP), AX 0x00a4 00164 (main.go:8)MOVQ 24(SP), CX 0x00a9 00169 (main.go:8)MOVQ AX, "".. autotmp_1+48(SP) 0x00ae 00174 (main.go:8)MOVQ CX, "".. autotmp_1+56(SP) 0x00b3 00179 (main.go:8)LEAQ "".. autotmp_1+48(SP), AX 0x00b8 00184 (main.go:8)MOVQ AX, (SP) 0x00bc 00188 (main.go:8)MOVQ $1, 8(SP) 0x00c5 00197 (main.go:8)MOVQ $1, 16(SP) 0x00ce 00206 (main.go:8)PCDATA $0, $1 0x00ce 00206 (main.go:8)CALL fmt.Println(SB) 0x00d3 00211 (main.go:9)MOVQ 88(SP), BP 0x00d8 00216 (main.go:9)ADDQ $96, SP 0x00dc 00220 (main.go:9)RET 0x00dd 00221 (main.go:7)PCDATA $0, $0 0x00dd 00221 (main.go:7)CALL runtime.panicindex(SB) 0x00e2 00226 (main.go:7)UNDEF 0x00e4 00228 (main.go:7)NOP 0x00e4 00228 (main.go:5)PCDATA $0, $-1 0x00e4 00228 (main.go:5)CALL runtime.morestack_noctxt(SB) 0x00e9 00233 (main.go:5)JMP 0Copy the code

FUNCDATA and PCDATA are compiler-generated compilations of Go language FUNCDATA and PCDATA that store garbage collection-related information, not care.

It doesn’t matter because the commands are simple, and our Go source code is simple enough that there’s no reason not to understand it.

Let’s start with a top down look at a few key functions:

CALL    runtime.makeslice(SB)
CALL    runtime.convT2Eslice(SB)
CALL    fmt.Println(SB)
CALL    runtime.morestack_noctxt(SB)
Copy the code
The serial number function
1 Create a slice
2 Type conversion
3 Print function
4 Stack space expansion

1 is related to creating slice; Type conversion; Calling fmt.Println requires a conversion of slice; 3 is the print statement. 4 is the stack space expansion function. At the beginning of the function, it will check whether the current stack space is enough, if not, it needs to be called to expand. We can ignore that for now.

Call the function will involve parameter passing, Go parameter passing is completed through the stack space. Next, let’s analyze the whole process in detail.

The number of rows role
1 mainFunction definition, stack frame size is96B
2-4 Determine whether the stack needs to be expanded and jump to it if so228, is called hereruntime.morestack_noctxt(SB)Perform stack expansion. The details will be covered in a follow-up article
5-9 willcaller BPPushing. I’ll talk more about that later
10-15 callruntime.makeslice(SB)Function and preparation. *_type stands forint, that is,sliceType of the element. The corresponding source here is line 6, the callmakecreatesliceThat line.510Representing length and capacity respectively, function arguments are prepared at the top of the stack before the function call command is executedCALL, into the stack frame of the called function, the sequence fromcallerThe top of the stack fetch function parameter
16 to 18 receivemakesliceThe return value of themoveMove to register
19-21 Index the array to2Assigns a value to the element2Because isintType ofslice, the element size is 8 bytes, soMOVQ $2, 16(AX)This command will2Move to index for2The location of the. It also checks the size of the index value and jumps to if it is out of bounds221, the implementation ofpanicfunction
22 to 26 Separate through registerAX, CX, DXmakesliceThe return value of themoveTo some other location in memory, also called a local variable, and that’s where it’s constructedslice

On the left is the data on the stack, on the right is the data on the heap. Array points to slice’s underlying data and is allocated to the heap. Notice that addresses on the stack grow from high to low; The heap grows from low to high. The number on the left of the stack indicates the number of lines of the corresponding assembly code, and the arrow on the right of the stack indicates the stack address. (48) SP, (56) SP

Note that in the diagram, the stack addresses grow from the bottom up, so SP represents the position of *_type in the diagram, and so on.

The number of rows role
27-32 Ready to callruntime.convT2Eslice(SB)Function parameter of
33-36 Receive the return value through the AX, CX registersmoveTo (48)SP, (56)SP

The function of convT2Eslice is declared as follows:

func convT2Eslice(t *_type, elem unsafe.Pointer) (e eface) 
Copy the code

The first argument is a pointer *_type, which is a structure representing the type of slice. The second argument is a pointer to the element, passing in the first address of slice’s underlying array.

The structure of the return value eface is defined as follows:

type eface struct {
	_type *_type
	data  unsafe.Pointer
}
Copy the code

Since we’ll call fmt.println (slice), take a look at the prototype function:

func Println(a ...interface{}) (n int, err error)
Copy the code

Println accepts the interface type, so we need to convert slice to interface. Slice is an “empty interface” because it has no methods. So convT2Eslice is called to complete the transformation.

The convT2Eslice function returns the type pointer and the data address. Call mallocGC to allocate a block of memory, copy the data into the new memory, and return the address of this memory. *_type returns the parameter passed in directly.

32(SP) and 40(SP) are returns from makeslice and can be ignored.

Println(slice) is the last function call left, so we continue.

The number of rows role
37 to 40 To preparePrintlnFunction parameters. There are three arguments, the first is the type address, and two more1, this temporarily do not know why to pass, have the understanding of the students can leave a message at the back of the article

So when you call fmt.println (slice), you’re actually passing in an eface address of type slice. In this way, Println can access the data in the type and eventually “print” it out.

Finally, let’s look at the beginning and end of the main stack frame.

0x0013 00019 (main.go:5)SUBQ $96, SP 0x0017 00023 (main.go:5)MOVQ BP, 88(SP) 0x001c 00028 (main.go:5)LEAQ 88(SP), BP................................................ 0x00d3 00211 (main.go:9)MOVQ 88(SP), BP 0x00d8 00216 (main.go:9)ADDQ $96, SP RETCopy the code

BP can be understood as saving the address at the bottom of the current function stack frame, while SP saves the address at the top of the stack.

Initially, BP and SP have an initial state respectively.

When the main function is executed, the new direction of SP is determined according to the stack frame size of main function, which makes the stack frame size of main function reach 96B. The old BP is then stored at the bottom of the main stack frame, and the BP register is redirected to the new stack bottom, that is, the bottom of the main stack frame.

Finally, when the main function is finished, the BP at the bottom of the stack is bounced back to the BP register to restore the initial state before the call. It’s like nothing happened. It’s the perfect scene.

This part, again detailed analysis of the function call process. On the one hand, let us review the content of the last article; On the other hand, I’ll show you how to find out what functions are actually called behind a function in Go. In this example, we see a makeslice call behind a make function. Also, to make assembly less intimidating, you can easily analyze something.

The interception

Interception is also a common way to create slices, either from an array or from a slice, specifying the start and end index positions.

Creating a new slice object based on an existing slice is called reslice. The new slice and the old slice share the underlying array, and changes made to the underlying array by both the new and old slices affect each other. New Slice objects created based on arrays have the same effect: changes made to arrays or slice elements affect each other.

It should be noted that the new slice and the old slice arrays interact only if they share the underlying array. If the new slice underlying array is expanded and moved to a new location due to the append operation, they do not interact. So, the question is whether the two will share the underlying array.

The interception operation adopts the following methods:

 data := [...]int{0.1.2.3.4.5.6.7.8.9}
 slice := data[2:4:6] // data[low, high, max]
Copy the code

Use three index values against data to intercept a new slice. Data can be an array or slice. Low is the lowest index value, and this is a closed interval, which means that the first element is data at the low index; High and Max are open ranges, meaning that the last element can only be the element at index high-1, and the maximum capacity can only be the element at index max-1.

max >= high >= low
Copy the code

When high == low, the new slice is null.

Also, high and Max must be within the size (CAP) of the old array or slice.

Take a look at an example from The 4th edition of Rain Mark’s Go Study Notes, page P43, with the open source book address in resources. Here I’ll expand and explain:

package main

import "fmt"

func main(a) {
	slice := []int{0.1.2.3.4.5.6.7.8.9}
	s1 := slice[2:5]
	s2 := s1[2:6:7]

	s2 = append(s2, 100)
	s2 = append(s2, 200)

	s1[2] = 20

	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(slice)
}
Copy the code

Let’s take a look at the results of the code run:

[2 3 20]
[4 5 6 7 100 200]
[0 1 2 3 20 5 6 7 100 9]
Copy the code

Let’s walk through the code. The initial state is as follows:

slice := []int{0.1.2.3.4.5.6.7.8.9}
s1 := slice[2:5]
s2 := s1[2:6:7]
Copy the code

S1 runs from slice index 2 (closed interval) to index 5 (open interval, where the element actually fetches to index 4), with a length of 3 and a capacity of 8, which defaults to the end of the array. S2 goes from index 2 (closed interval) of S1 to index 6 (open interval, the element is actually taken to index 5), and has a capacity of 5 to index 7 (open interval, the element is actually taken to index 6).

Next, we append the element 100 to the end of s2:

s2 = append(s2, 100)
Copy the code

S2 has just enough capacity. However, this modifies the element at the corresponding position in the original array. This change is visible to both the array and s1.

Append element 200 to s2 again:

s2 = append(s2, 100)
Copy the code

At this point, the capacity of S2 is insufficient and it needs to be expanded. So S2 starts all over again, replicating the original elements in new locations to expand its capacity. In addition, in order to cope with the potential expansion of append in the future, S2 will leave some more buffer during the expansion and expand the new capacity to twice the original capacity, that is, 10.

Finally, modify the element at position 2 in s1:

s1[2] = 20
Copy the code

This time only affects the elements at the corresponding positions in the original array. It doesn’t affect S2 anymore, she’s already gone.

One more thing, when you print s1, you only print elements up to s1. So, only three elements are printed, even though its underlying array has more than three elements.

As for how they share the underlying array at the assembly level, we won’t go into that for space. Interested students can reply on the background of the official account: Section interception.

I’ll give you a detailed analysis of the function call relationship and a clear view of the behavior of sharing the underlying array. The QR code is at the bottom of the article.

What’s the difference between slice and array

Slice’s underlying data is an array. Slice is a wrapper around an array that describes a fragment of an array. Both can access individual elements with subscripts.

Arrays are of fixed length and cannot be changed once the length is defined. In Go, arrays are uncommon because their length is part of a type, limiting their expressiveness. For example, [3]int and [4]int are different types.

Slicing is very flexible and can be dynamically expanded. The type of slice has nothing to do with the length.

What exactly does Append do

Let’s look at the prototype of the append function:

func append(slice []Type, elems ... Type) []Type
Copy the code

The append function takes variable length arguments, so you can append multiple values to slice, and you can use… To pass a slice, append a slice directly.

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
Copy the code

The append function returns a new slice, and the Go compiler does not allow calls to append without using the return value.

append(slice, elem1, elem2)
append(slice, anotherSlice...)
Copy the code

So the above usage is wrong and will not compile.

Append allows you to append elements to Slice, essentially adding elements to the underlying array. But the length of the underlying array is fixed, and if the element pointed to by index len-1 is already the last element in the underlying array, it cannot be added.

Slice moves to the new memory location and the length of the new underlying array increases so that new elements can be placed. At the same time, the length of the new underlying array, the size of the new slice, is reserved to buffer for future append operations. Otherwise, every time an element is added, it will be migrated and the cost will be too high.

The size of buffer reserved for a new slice is regular. Most articles on the Internet describe it this way:

When the original slice capacity is less than 1024, the new slice capacity is doubled. When the original slice capacity exceeds 1024, the new slice capacity becomes 1.25 times of the original capacity.

I’ll start with the conclusion: the above description is wrong.

To show that the above rule is wrong, I wrote a little toy code:

package main

import "fmt"

func main(a) {
	s := make([]int.0)

	oldCap := cap(s)

	for i := 0; i < 2048; i++ {
		s = append(s, i)

		newCap := cap(s)

		ifnewCap ! = oldCap { fmt.Printf("[%d -> %4d] cap = %-4d | after append %-4d cap = %-4d\n".0, i- 1, oldCap, i, newCap)
			oldCap = newCap
		}
	}
}
Copy the code

I create an empty slice and append new elements in a loop. The change in capacity is then recorded, and each time the capacity changes, the old capacity and the capacity after the addition of elements are recorded, as well as the elements in slice at that time. That way, I can look at how the volume of new and old slices varies to find patterns.

Running results:

[0 ->   -1] cap = 0     |  after append 0     cap = 1   
[0 ->    0] cap = 1     |  after append 1     cap = 2   
[0 ->    1] cap = 2     |  after append 2     cap = 4   
[0 ->    3] cap = 4     |  after append 4     cap = 8   
[0 ->    7] cap = 8     |  after append 8     cap = 16  
[0 ->   15] cap = 16    |  after append 16    cap = 32  
[0 ->   31] cap = 32    |  after append 32    cap = 64  
[0 ->   63] cap = 64    |  after append 64    cap = 128 
[0 ->  127] cap = 128   |  after append 128   cap = 256 
[0 ->  255] cap = 256   |  after append 256   cap = 512 
[0 ->  511] cap = 512   |  after append 512   cap = 1024
[0 -> 1023] cap = 1024  |  after append 1024  cap = 1280
[0 -> 1279] cap = 1280  |  after append 1280  cap = 1696
[0 -> 1695] cap = 1696  |  after append 1696  cap = 2304
Copy the code

The new slice is indeed twice as large as the old slice when the old slice capacity is less than 1024. So far, so good.

However, things change when the old Slice capacity is greater than or equal to 1024. When the element 1280 was added to slice, the old slice had a capacity of 1280, then 1696, which is not a 1.25x relationship (1696/1280=1.325). After the addition of 1696, the new capacity 2304 is not 1.25 times that of 1696.

It can be seen that the expansion strategy in various articles on the Internet is not correct. We directly out of the source code: before the source code, no secrets.

As we saw in the assembly code above, the growslice function is called when adding elements to slice when there is not enough capacity, so let’s go straight to the code.

/ / go 1.9.5 SRC/runtime/slice. Go: 82
func growslice(et *_type, old slice, cap int) slice {
    / /...
    newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			for newcap < cap {
				newcap += newcap / 4}}}/ /...
	
	capmem = roundupsize(uintptr(newcap) * ptrSize)
	newcap = int(capmem / ptrSize)
}
Copy the code

See? If you just look at the first half, newCap’s rule is true in various articles on the web. In reality, the second half also does a memory alignment for NewCap, which is related to the memory allocation strategy. After memory alignment, the size of the new slice should be greater than or equal to 2 or 1.25 times the size of the old slice.

After that, the Go memory manager requests memory, copies the data from the old slice, and adds the append elements to the new underlying array.

Finally, a new slice is returned to the growslice caller with the same length but an increased capacity.

A final example of appends is the Golang Slice Expansion Rule from the Resources section.

package main

import "fmt"

func main(a) {
	s := []int{1.2}
	s = append(s,4.5.6)
	fmt.Printf("len=%d, cap=%d".len(s),cap(s))
}
Copy the code

The results are as follows:

len=5, cap=6
Copy the code

If the original slice length is less than 1024, the capacity is doubled each time, as summarized in various articles on the Internet. When you add element 4, the capacity becomes 4; Element 5 is added unchanged; Adding element 6 doubles the capacity to 8.

The result of the above code is:

len=5, cap=8
Copy the code

This is wrong! Let’s take a closer look at why this is so, and bring up the code again:

/ / go 1.9.5 SRC/runtime/slice. Go: 82
func growslice(et *_type, old slice, cap int) slice {
    / /...
    newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		/ /...
	}
	/ /...
	
	capmem = roundupsize(uintptr(newcap) * ptrSize)
	newcap = int(capmem / ptrSize)
}
Copy the code

This function takes, in turn, the type of the element, the old slice, and the minimum size of the new slice.

In this example, s has only 2 elements, len and cap are both 2. After append three elements, the length is 3, and the minimum size is 5. The cap = 5. On the other hand, Doublecap is twice the size of slice, equal to 4. The first if condition is satisfied, so newCap becomes 5.

Then we call roundupsize, passing in 40. (ptrSize in code refers to the size of a pointer, 8 on 64-bit machines)

Roundupsize = roundupsize

// src/runtime/msize.go:13
func roundupsize(size uintptr) uintptr {
	if size < _MaxSmallSize {
		if size <= smallSizeMax- 8 - {
			return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv- 1)/smallSizeDiv]])
		} else {
			/ /...}}/ /...
}

const _MaxSmallSize = 32768
const smallSizeMax = 1024
const smallSizeDiv = 8
Copy the code

Obviously, we will eventually return the result of this formula:

class_to_size[size_to_class8[(size+smallSizeDiv- 1)/smallSizeDiv]]
Copy the code

These are two slices of memory allocation in the Go source code. Class_to_size Obtains the size of an object divided by span by spanClass. Size_to_class8 represents the spanClass that gets it by size.

var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0.1.2.3.3.4.4.5.5.6.6.7.7.8.8.9.9.10.10.11.11.12.12.13.13.14.14.15.15.16.16.17.17.18.18.18.18.19.19.19.19.20.20.20.20.21.21.21.21.22.22.22.22.23.23.23.23.24.24.24.24.25.25.25.25.26.26.26.26.26.26.26.26.27.27.27.27.27.27.27.27.28.28.28.28.28.28.28.28.29.29.29.29.29.29.29.29.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31.31.31.31.31.31.31.31.31.31.31.31.31.31.31.31}

var class_to_size = [_NumSizeClasses]uint16{0.8.16.32.48.64.80.96.112.128.144.160.176.192.208.224.240.256.288.320.352.384.416.448.480.512.576.640.704.768.896.1024.1152.1280.1408.1536.1792.2048.2304.2688.3072.3200.3456.4096.4864.5376.6144.6528.6784.6912.8192.9472.9728.10240.10880.12288.13568.14336.16384.18432.19072.20480.21760.24576.27264.28672.32768}
Copy the code

The size we passed in is equal to 40. (size+ smallsizediv-1)/smallSizeDiv = 5; Get the element with index 5 in size_to_class8 to be 4; Gets the element with index 4 in class_to_size as 48.

Finally, the new slice has a capacity of 6:

newcap = int(capmem / ptrSize) / / 6
Copy the code

As for the origin of the above two magic arrays, I will not expand for the moment.

Why can nil slice append directly

In fact, nil slice or empty slice can be called append function to get the underlying array expansion. The result is a call to mallocGC to request a chunk of memory from Go’s memory manager, which then assigns the original nil or empty slice, and then transforms into a “real” slice.

What is the difference between passing a slice and a slice pointer

As mentioned earlier, slice is a structure that contains three members: Len, Cap, and array. Represents section length, capacity, and address of underlying data respectively.

Slice, when used as a function argument, is a normal structure. It’s easy to understand: if slice is passed directly, the argument slice is not changed by the operation in the function from the caller’s point of view. Passing a pointer to slice will, in the caller’s view, alter the original slice.

If the slice underlying array data is changed, the slice underlying data will be reflected regardless of whether the slice or slice pointer is passed. Why can I change the data in the underlying array? Easy to understand: the underlying data is a pointer to the slice structure, even though the slice structure itself is not changed, i.e. the underlying data address is not changed. But by pointing to the underlying data, you can change the underlying data of the slice, no problem.

Slice’s Array field is used to retrieve the array’s address. In the code, slice’s underlying array element values are changed directly by operations like s[I]=10.

In addition, the Go language function parameters are passed, only the value, no reference passed. A related article will be written later, so stay tuned.

Let’s take a look at another snippet of code that is young and ignorant:

package main

func main(a) {
	s := []int{1.1.1}
	f(s)
	fmt.Println(s)
}

func f(s []int) {
	// I is just a copy and cannot change the value of the element in s
	/*for _, i := range s { i++ } */

	for i := range s {
		s[i] += 1}}Copy the code

Run the program and output:

[2 2 2]
Copy the code

It does change the underlying data in the original slice. What is passed here is a copy of slice. In f, s is just a copy of s in main. Inside f, the effect on s does not change s in the outer main function.

To actually change the outer slice, either assign the returned new slice to the original slice or pass a pointer to the slice to the function. Let’s look at another example:

package main

import "fmt"

func myAppend(s []int) []int {
	// The change in s does not affect the outer function s
	s = append(s, 100)
	return s
}

func myAppendPtr(s *[]int) {
	// will change the outer layer s itself
	*s = append(*s, 100)
	return
}

func main(a) {
	s := []int{1.1.1}
	newS := myAppend(s)

	fmt.Println(s)
	fmt.Println(newS)

	s = newS

	myAppendPtr(&s)
	fmt.Println(s)
}
Copy the code

Running results:

[1 1 1 100] [1 1 1 100 100]Copy the code

In myAppend, the change in s is just passing a value and does not affect the outer s, so the first line will still print [1, 1, 1].

NewS is a new slice, which is based on S. So it prints the result of adding a 100: [1 1 1 100].

Finally, newS is assigned to S, where S actually becomes a new slice. After that, we pass an S pointer to myAppendPtr and this time it really changes: [1 1 1 100 100].

conclusion

That’s the end of Slice, if you enjoyed it. Let’s conclude by saying:

  • A slice is an abstraction of the underlying array, describing a fragment of it.
  • A slice is actually a structure with three fields: length, capacity, and the address of the underlying data.
  • Multiple slices may share the same underlying array, in which case changes to one slice or the underlying array affect the other slices.
  • appendThe function is called when the slice capacity is insufficientgrowsliceThe function retrieves as much memory as it needs, which is called scaling, and scaling changes the original location of the element.
  • The expansion strategy is not simply to expand the original slice capacity2Times or1.25Times, and memory alignment. The capacity after expansion is greater than or equal to the original capacity2Times or1.25Times.
  • When slice is directly used as a function parameter, the element of slice can be changed, but the slice itself cannot be changed. If you want to change the slice itself, you can return the changed slice and the function caller receives the changed slice or takes the slice pointer as a function parameter.

Finally, if you think this article is helpful to you, please help me click the “recommendation” in the lower right corner, thank you!

The resources

[Code hole “In-depth Analysis of the Three Special States of” Slice “in Go language”] juejin.cn/post/684490… 【 old money array 】juejin.cn/post/684490… 【 old money slice 】juejin.cn/post/684490… 】 【 golang interface source code i6448038. Making. IO / 2018/10/01 /… 】 【 golang interface source code legendtkl.com/2017/07/01/… 【interface】www.jishuwen.com/d/2C9z#tuit 【 Rain trace open source Go learning notes 】github.com/qyuhen/book 【 Slice map is beautiful 】halfrost.com/go_slice/ 【Golang Slice the expansion rules 】 jodezer. Making. IO / 2017/05 / gol… Slice as parameters 】 【 www.cnblogs.com/fwdqxl/p/93… Ictar.xyz /2018/10/25/… Append mechanism translation 】 【 brantou. Making. IO / 2017/05/24 /… 【slice compilation 】xargin.com/go-slice/ 【 Slice tricks】colobu.com/2017/03/22/… Have figure 】 【 i6448038. Making. IO / 2018/08/11 /… The essence of the slice 】 【 www.flysnow.org/2018/12/21/… Blog.thinkeridea.com/201901/go/s slice using techniques 】 【… Slice/array, memory growth blog.thinkeridea.com/201901/go/s 】…