You're being led astray by some unconventional terminology. A 2D array is always declared as
T arr[M][N]; // for some arbitrary type T and size expressions
// M and N
and the declaration can be read as "arr
is an M
-element array of N
-element arrays of T
".
Your "semi-dynamic" array is a 1D array of pointers:
int *arr[M];
each element may point to the first element of another array (whether dynamically allocated or not)
for ( size_t i = 0; i < M; i++ )
arr[i] = malloc( sizeof *arr[i] * N ); // allocates space for N ints
And your "fully dynamic" array
int **arr;
isn't an array at all. It's just a pointer. It may point to the first element of an array of pointers (dynamically allocated or not), each element of which may point to the first element of an array (dynamically allocated or not):
arr = malloc( sizeof *arr * M ); // Allocates space for M pointers to int
for ( size_t i = 0; i < M; i++ )
arr[i] = malloc( sizeof *arr[i] * N ); // Allocates space for N ints.
Assuming M
and N
are 2
, your 2D array will be laid out as
+---+
arr: | | arr[0][0]
+---+
| | arr[0][1]
+---+
| | arr[1][0]
+---+
| | arr[1][1]
+---+
whereas your 1D array of pointers will be laid out as
+---+ +---+
arr: | | arr[0] ----------------------> | | arr[0][0]
+---+ +---+
| | arr[1] -----------+ | | arr[0][1]
+---+ | +---+
|
| +---+
+----------> | | arr[1][0]
+---+
| | arr[1][1]
+---+
and your pointer-to-pointer version will look like
+---+ +---+ +---+
arr: | | --> | | arr[0] -------------> | | arr[0][0]
+---+ +---+ +---+
| | arr[1] -------+ | | arr[0][1]
+---+ | +---+
|
| +---+
+-----> | | arr[1][0]
+---+
| | arr[1][1]
+---+
Again, there's no rule that your pointers have to point to dynamically-allocated memory. The following is also valid (although I don't have a good use case for it at the moment):
int x[2];
int y[4];
int *arr[2] = {x, y};
which gives us
+---+ +---+
arr: | | arr[0] --------------> x: | | x[0] (arr[0][0])
+---+ +---+
| | arr[1] -------+ | | x[1] (arr[0][1])
+---+ | +---+
|
| +---+
+------> y: | | y[0] (arr[1][0])
+---+
| | y[1] (arr[1][1])
+---+
| | y[2] (arr[1][2])
+---+
| | y[3] (arr[1][3])
+---+
x
and y
are not dynamically allocated; they're the same storage class as arr
.