The internals of slice


There are 3 components of slice:
a) Pointer: Points to the start position of slice in the underlying array;
b) length (type is int): the number of the valid elements of the slice;
b) capacity (type is int): the total number of slots of the slice.

Check the following code:

  1. package main
  2. import (
  3. "fmt"
  4. "unsafe"
  5. )
  6. func main() {
  7. var s1 []int
  8. fmt.Println(unsafe.Sizeof(s1))
  9. }

The result is 24 on my 64-bit system (The pointer and int both occupy 8 bytes).

In the next example, I will use gdb to poke the internals of slice. The code is like this:

  1. package main
  2. import "fmt"
  3. func main() {
  4. s1 := make([]int, 3, 5)
  5. copy(s1, []int{1, 2, 3})
  6. fmt.Println(len(s1), cap(s1), &s1[0])
  7. s1 = append(s1, 4)
  8. fmt.Println(len(s1), cap(s1), &s1[0])
  9. s2 := s1[1:]
  10. fmt.Println(len(s2), cap(s2), &s2[0])
  11. }

Use gdb to step into the code:

  1. 5 func main() {
  2. (gdb) n
  3. 6 s1 := make([]int, 3, 5)
  4. (gdb)
  5. 7 copy(s1, []int{1, 2, 3})
  6. (gdb)
  7. 8 fmt.Println(len(s1), cap(s1), &s1[0])
  8. (gdb)
  9. 3 5 0xc820010240

Before executing “s1 = append(s1, 4)“, fmt.Println outputs the length(3), capacity(5) and the starting element address(0xc820010240) of the slice, let’s check the memory layout of s1:

  1. 10 s1 = append(s1, 4)
  2. (gdb) p &s1
  3. $1 = (struct []int *) 0xc82003fe40
  4. (gdb) x/24xb 0xc82003fe40
  5. 0xc82003fe40: 0x40 0x02 0x01 0x20 0xc8 0x00 0x00 0x00
  6. 0xc82003fe48: 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  7. 0xc82003fe50: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  8. (gdb)

Through examining the memory content of s1(the start memory address is 0xc82003fe40), we can see its content matches the output of fmt.Println.

Continue executing, and check the result before “s2 := s1[1:]“:

  1. (gdb) n
  2. 11 fmt.Println(len(s1), cap(s1), &s1[0])
  3. (gdb)
  4. 4 5 0xc820010240
  5. 13 s2 := s1[1:]
  6. (gdb) x/24xb 0xc82003fe40
  7. 0xc82003fe40: 0x40 0x02 0x01 0x20 0xc8 0x00 0x00 0x00
  8. 0xc82003fe48: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  9. 0xc82003fe50: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00

We can see after appending a new element(s1 = append(s1, 4)), the length of s1 is changed to 4, but the capacity remains the original value.

Let’s check the internals of s2:

  1. (gdb) n
  2. 14 fmt.Println(len(s2), cap(s2), &s2[0])
  3. (gdb)
  4. 3 4 0xc820010248
  5. 15 }
  6. (gdb) p &s2
  7. $3 = (struct []int *) 0xc82003fe28
  8. (gdb) x/24hb 0xc82003fe28
  9. 0xc82003fe28: 0x48 0x02 0x01 0x20 0xc8 0x00 0x00 0x00
  10. 0xc82003fe30: 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  11. 0xc82003fe38: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00

The element start address of s2 is 0xc820010248, actually the second element of s1(0xc82003fe40), and the length(3) and capacity(4) are both one less than the counterparts of s1(4 and 5 respectively).