Arrays are not pointers (this point cannot be stressed enough).
That being said, an array decays to a pointer to its first element. For example:
int a[10];
int b[20][30];
void print_a(int *);
void print_b(int (*)[30]);
print_a(a);
print_b(b);
The first element of a
is a[0]
, and similarly the first element of b
is b[0]
. You basically take that first dimension away, and change it to a (*)
; I'll explain more in a moment since it is a bit more complex than that.
The relationship between pointers and arrays is riddled with contextual subtleties that aren't terribly difficult to grasp, but the size information in various scopes makes it interesting and also helps to give you an idea of how the decay works:
#include <stdio.h>
int h(int *pa)
{
printf("h(int *): sizeof pa=%zu\n", sizeof pa);
printf("h(int *): sizeof *pa=%zu\n", sizeof *pa);
return *pa;
}
int g(int (*pa)[5])
{
printf("g(int (*)[5]): sizeof pa=%zu\n", sizeof pa);
printf("g(int (*)[5]): sizeof *pa=%zu\n", sizeof *pa);
return h(*pa);
}
int f(int (*pa)[3][5])
{
printf("f(int (*)[3][5]): sizeof pa=%zu\n", sizeof pa);
printf("f(int (*)[3][5]): sizeof *pa=%zu\n", sizeof *pa);
return g(*pa);
}
int main(void)
{
int arr[2][3][5] = {{{11235}}};
printf("main: sizeof arr=%zu\n", sizeof arr);
printf("main: sizeof *arr=%zu\n", sizeof *arr);
printf("%d\n", f(arr));
}
Every pointer is the same size (this may not always be true on all platforms!), but by dereferencing the pointer, you see the size of a given element of the array, whether you dereference using the unary *
operator or the [N]
array notation, which is equivalent to *((array)+(N))
by definition.
Anyway, going back to the difference between pointers and arrays, you should understand that int[20][30]
is not the same as int **
. Why is that? Because of the fact that int[20][30]
decays to a pointer of type int(*)[30]
, no more decay can occur until the pointer is dereferenced. Moreover, int **
is actually int *(*)
, which means it can point to the first element of an array of pointers. That is, int **
might have once been int *[N]
.
int foo[x][y][z] <=> int (*foo)[y][z]
int *foo[m][n] <=> int *(*foo)[n]
int (*foo[a])[b] <=> int (**foo)[b]
In the first case, we have a 3-D array, which decays to a pointer to a 2-D array; in other words, an array of arrays and a pointer to an array are closely related and interchangeable in many contexts aside from the size issue. The first dimension x
is the one that decays, leaving the y
and z
dimensions.
In the second case, we have a 2-D array of pointers. This decays to a pointer to an array of pointers. Again, an array of arrays is closely related to a pointer to an array, and dimension m
decays, leaving dimension n
.
In the third case, we have an array of pointers to arrays. It decays to a pointer to a pointer to an array. Since dimension a
is closest to the variable name, that is the one that decays, leaving dimension b
. Note that since it is an array of pointers to arrays, the pointers could point to the first element of arrays themselves:
int arr[2][3][5];
int (*foo[2])[5] = { arr[0], arr[1] };
int (**foo_ptr)[5] = foo;
Recap:
- Array (size A) of arrays (size B) <=> Pointer to array (size B)
- Array (size A) of pointers <=> Pointer to pointer
- The array that decays/grows is always the innermost array/pointer, the innermost being the one closest to the variable name in the variable declaration, with arrays having higher associativity than pointers, though of course parentheses make all the difference.
This rabbit hole obviously can be confusing, but I hope I helped a bit at least!