This is something that comes up a lot, so I will attempt to explain it as clearly as I can.
When you make an array, it stores the elements contiguously in memory, so:
int arr[2] = { 1, 2 };
Translates to:
arr:
+---+---+
| 1 | 2 |
+---+---+
A pointer points to an object in memory, and when dereferenced, via unary *
or via []
, it accesses that contiguous memory. So after
int *ptr = arr;
ptr
(or &ptr[0]
if you like) points to the box 1
is in, and ptr + 1
(or &ptr[1]
) points to the box 2
is in. This makes sense.
But if arrays are contiguous in memory, arrays of arrays are contiguous in memory. So:
int arr[2][2] = {{ 1, 2 }, { 3, 4 }};
Looks in memory like this:
arr:
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
Which looks a lot like our flat array.
Now, let's consider how a pointer to a pointer to an int
would be laid out in memory:
ptr:
+-------+-------+
| &sub1 | &sub2 |
+-------+-------+
sub1:
+---+---+
| 1 | 2 |
+---+---+
sub2:
+---+---+
| 3 | 4 |
+---+---+
ptr
(or &ptr[0]
) points to sub1
, and ptr + 1
(or &ptr[1]
) points to sub2
. sub1
and sub2
have no actual relation to each other, and can be anywhere in memory, but because it's a pointer to a pointer, the double-dereference of a 2D array is preserved, even though the memory structure is not compatible.
Arrays of type T
decay to pointers to type T
, but arrays of arrays of type T
do not decay to pointers to pointers to type T
, they decay to pointers to arrays of type T
. So when our 2D arr
decays to a pointer, it is not a pointer to a pointer to an int
, but a pointer to an int [2]
. The full name of this type is int (*)[2]
, and to make your line of code work you'd want to use
int (*ptr)[2] = arr;
Which is the correct type. ptr
expects to point to a contiguous array of memory, like arr
does - ptr
(or &ptr[0]
) points to arr
and ptr + 1
(or &ptr[1]
) points to &arr[1]
. ptr[0]
points to the box that holds 1
, and ptr[1]
points to the box that holds 3
, so ptr[0][0]
yields 1, ptr[0][1]
yields 2, and so on.
Why do you need to know this? 2D pointers seem more complicated than they're worth - if you were using malloc
you'd have to call malloc
repeatedly in a loop, and do the same for free
. OR, you could use some evil* trickery to make a flat, 1-dimensional allocation of memory act like a 2D array:
// x and y are the first and second dimensions of your array
// so it would be declared T arr[x][y] if x and y were static
int (*arr)[y] = malloc(x * y * sizeof(arr[0][0]));
if(!arr) /* error */;
Now arr
points to a contiguous block of arrays of size y
of int
objects. Since the object it points to is an array, we don't need the double-pointer-indirection of int **
objects, and when you're done, you can free it with one call:
free(arr);
Compare this to a version using int **
:
int **arr = malloc(x * sizeof(*arr));
if(!arr) /* error */;
for(size_t ii = 0; ii < x; ii++)
{
arr[ii] = malloc(y * sizeof(**arr));
if(!arr[ii])
{
free(arr[ii]);
free(arr);
}
}
// do work
for(size_t ii = 0; ii < x; ii++)
free(arr[ii]);
free(arr);
The above code has a memory leak. See if you can find it. (Or just use the version with those seemingly tricky pointers-to-arrays.)