16

In Java, two dimensional array is multi one-dimensional array. That means those one-dimensional arrays not consecutive on memory.

In contrast, in C, two dimensional array in fact is one-dimensional array with size is total_row * total_column. Because Go language uses many concepts from C.

So my question is: does two dimensional array's memory representation in Go look like C or Java?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Trần Kim Dự
  • 5,872
  • 12
  • 55
  • 107
  • 3
    And why put you java and c tags on your question, when it is solely about Go? – GhostCat Sep 18 '16 at 18:25
  • 1
    Because I think my question has some information based on Java and C language. So I think it will suitable for putting those tags on my question. I'm sorry if my thinking is wrong. – Trần Kim Dự Sep 18 '16 at 18:27
  • 1
    @TrầnKimDự While the question is based on what you already know from C and Java, the question is not about C or Java themselves. I've removed those tags. – code_dredd Sep 18 '16 at 19:36

2 Answers2

42

In Go, slices are often mistaken for arrays, so I answer regarding both.

Arrays

Quoting from the spec: Array types:

Array types are always one-dimensional but may be composed to form multi-dimensional types.

There you have your answer plain and clear. But the answer is not just that as arrays are values in Go, they are not descriptors (headers) like slices.

See this simple example:

x := [5][5]byte{}
fmt.Println(&x[0][3])
fmt.Println(&x[0][4])
fmt.Println(&x[1][0])

Output (try it on the Go Playground):

0x10432203
0x10432204
0x10432205

As you can see, the memory allocated and used for the array is contiguous: the second row starts at a memory address that is the subsequent to the address of the last element of the first row.

Checking size of arrays:

fmt.Println(unsafe.Sizeof([4][6]int{})) // 96
fmt.Println(unsafe.Sizeof([6][4]int{})) // 96

It doesn't matter if you switch rows and columns, its size is the same.

Slices

The same applies in case of slices: a multidimensional slice is a slice of slices. Spec: Slice types:

A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array.
...
Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects.

Slices are descriptors, a slice header contains a pointer to an element of an underlying (backing) array, a length and a capacity. So the number of total slices matters in terms of memory usage.

See this example:

x := make([][]byte, 2)
for i := range x {
    x[i] = make([]byte, 1000)
}
fmt.Println(len(x), len(x)*len(x[0]))

y := make([][]byte, 1000)
for i := range y {
    y[i] = make([]byte, 2)
}
fmt.Println(len(y), len(y)*len(y[0]))

Output (try it on the Go Playground):

2 2000
1000 2000

Both x and y multidimensional slices have 2000 elements total (2000 bytes), but x stores 2 slices only, each having 1000 elements, while y stores 1000 slices, each having 2 elements.

This means x requires 2 slice headers while y requires 1000 slice headers (for the elements, +1 for x and y themselves)!

A slice header is represented by reflect.SliceHeader:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

The size of a slice header on 32-bit architectures is 12 bytes, on 64 bit architectures its 24 bytes. So in case of 32-bit arch elements of x require 2,000 bytes plus 2x12 bytes in memory which is 2,024 bytes, while elements of y require 2,000 bytes plus 1,000*12 which is 14,000 bytes.

Also note that elements of a multidimensional slice may contain slices with different lengths:

With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.

Like in this example:

x := make([][]byte, 2)
x[0] = []byte{1, 2}
x[1] = []byte{1, 2, 3, 4}
fmt.Println(x[0])
fmt.Println(x[1])

Output (try it on the Go Playground):

[1 2]
[1 2 3 4]

If you haven't read, recommended: The Go Blog: Arrays, slices (and strings): The mechanics of 'append'

icza
  • 389,944
  • 63
  • 907
  • 827
  • 1
    thanks for your dedicated answer. I have learned so much from your answer :D – Trần Kim Dự Sep 18 '16 at 19:27
  • As the blog stated, I think Go designs good at this point, combine good part from both C and Java array. – Trần Kim Dự Sep 18 '16 at 19:31
  • Just want to add that essentially, a slice with type `[][]string` is simply storing data that satisfies the type of `[]string`, which then holds the actual pointer, cap and len to the underlying array that actually store the string value. – hafizhanindito Nov 02 '18 at 07:47
  • hi, awesome answer, I know that one dimensional array will be stored contiguous, but can you explain how multidimensional slice backing array will be stored on memory ? are inner slices backing array stored separately in memory ? – mohammad Feb 01 '21 at 07:06
  • 1
    @mohammad Slice is just a header, referring to a backing array. So if you have a slice of slices, that just contains headers, which may point to anywhere. You can't even allocate backing arrays for a slice of slices in one step, only the slice of headers. You have to allocate and initialize the slices one-by-one, so it's most likely they won't end up continuous in memory. You may however allocate a big array (or slice), and reslice that to initialize a slice of slices, in which case you would have the backing arrays allocated continuous. – icza Feb 01 '21 at 07:27
  • great answer. Just one question: Is there any way to print out the total size of a multidimensional slice? – starriet Apr 12 '22 at 13:25
  • @starriet What is the total size? Memory size in bytes? Number of elements? For the former, see [How to get memory size of variable in Go?](https://stackoverflow.com/questions/44257522/how-to-get-memory-size-of-variable-in-go/44258164#44258164), for the latter, iterate over the slice and sum the element counts. – icza Apr 12 '22 at 13:31
  • @icza Thanks. I was wondering if there is any "automatic" method to calculate the whole memory size of an N-dimensional slice. I think one should write a function to do that recursively. Counting the total number of elements would be done in a similar way I guess. – starriet Apr 12 '22 at 14:25
1

For me, slice in Go seems like vector in C++. Vector also has 3 key data members : ptr to data, capacity and length. And if vector is 2d, the inner lengths of subvectors may vary dynamically as well.

In comparsion, array in Go seems like array in C.

Martin Gao
  • 61
  • 4