2D Array
Initialisation
Inline initialisation
A 2D array can be initialise as follows:
char arr[][3] = {
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'g', 'h', 'k'},
};
Or we can specify the size
char arr[][3] = {
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'g', 'h', 'k'},
};
Memory structure
The memory structure in here will be different than in the malloc one. Since we declare as an array, it will be within 1 contiguous block.

With pointer working like this:

Malloc
We first need to malloc the array. And loop through and malloc each block of array:
char **arr = (char**) malloc(sizeof(char*) * 3);
for(int i =0; i < 3; i++) {
arr[i] = malloc(sizeof(char*) * 3);
}
Memory structure
For the first line, we basically only malloc 3 pointers:

However, to assign the elements inside these pointers, we need to also malloc at each of the pointer:

And now we can fill the elements inside the blue one. After mallocing, this is how the structure memory works:

Subscripting
When selecting an element, following the same concept of Array supscripting, we have:
arr[2][1] = *(*(arr + 2) + 1)
Let's break it down:
arr[2]will select the value ofp3pointer.- Address of
p3isarr + 2. This pointer will storervalueof memory address ofarr[2]. Therefore it's*(arr + 2)
- Address of
arr[2][1]will select the value ofarr[2] + 1pointer- From the above, we know that
arr[2]will return the value of&arr[2][0]now if we have[1]it's simply justarr[2] + 1. - Which means it will be
*(arr[2] + 1)or*(*arr + 2) + 1)
- From the above, we know that
Memory visualiser
Malloc

Note that in here even though p3 (arr + 2) points to arr[2][0] directly. You cannot do *(arr + 2) to get arr[2][0] since the type is different. arr + 2 is pointing to the array representation type and we're trying to get the value of arr[2][0].
A way to bypass this is to cast *(arr + 2) to void* and try to read it as it: