Let's visualize what you're trying to do first, and then I'll show the code to get there:
int *** int ** int * int
+---+ +---+ +---+ +---+
a | | ---> a[0] | | ------> a[0][0] | | ----> a[0][0][0] | |
+---+ +---+ +---+ +---+
a[1] | | ----+ a[0][1] | | -+ a[0][0][1] | |
+---+ | +---+ | +---+
a[2] | | | ... | ...
+---+ | +---+ | +---+
| a[0][h-1] | | | a[0][0][w-1] | |
| +---+ | +---+
| |
| +---+ | +---+
+-> a[1][0] | | +--> a[0][1][0] | |
+---+ +---+
a[1][1] | | a[0][1][1] | |
+---+ +---+
... ...
+---+ +---+
a[1][h-1] | | a[0][1][w-1] | |
+---+ +---+
Types:
- Each
a[i][j][k]
has type int
;
- Each
a[i][j]
points to the first element of a sequence of int
objects, so it must have type int *
;
- Each
a[i]
points to the first element of a sequence of int *
objects, so it must have type int **
;
a
points to the first element of a sequence of int **
objects, so it must have type int ***
.
Since you're doing a piecemeal nested allocation, you need to check the result of each malloc
call, and if there's an error, you will want to clean up any previously allocated memory before bailing out, otherwise you risk memory leaks. Unfortunately, there's no really clean or elegant way to do that - you either carry a flag around and do a bunch of extra tests, or you throw in a couple of goto
s. I'm going to show two different approaches, neither of which is all that pretty.
First method - as we allocate each a[i]
, we also allocate each a[i][j]
(a "depth-first" approach) and initialize each a[i][j][k]
. If an allocation on a[i][j]
fails, we must deallocate all of a[i][0]
through a[i][j-1]
, then deallocate a[i]
, then repeat the process for each of a[0]
through a[i-1]
:
/**
* Depth-first approach: allocate each a[i][j] with each a[i]
*/
int ***alloc1( size_t pages, size_t height, size_t width )
{
size_t i, j, k;
int ***a = malloc( sizeof *a * pages ); // allocate space for N int **
// objects, where N == pages
if ( !a )
return NULL;
for ( i = 0; i < pages; i++ )
{
a[i] = malloc( sizeof *a[i] * height ); // for each a[i], allocate space for
if ( !a[i] ) // N int * objects, where N == height
goto cleanup_1;
for ( j = 0; j < height; j++ )
{
a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate
if ( !a[i][j] ) // space for N int objects,
goto cleanup_2; // where N == w
for ( k = 0; k < width; k++ )
a[i][j][k] = initial_value( i, j, k );
}
}
goto done;
/**
* Free all of a[i][0] through a[i][j-1], then free a[i]
*/
cleanup_2:
while ( j-- )
free( a[i][j] );
free( a[i] );
/**
* Free all of a[0] through a[i-1], then free a
*/
cleanup_1:
while ( i-- )
{
j = height;
goto cleanup_2;
}
free( a );
a = NULL;
done:
return a; // at this point, a is either a valid pointer or NULL.
}
Yes, this code contains goto
s, and it violates one of my pet rules (never branch backwards). But, there's a fairly clean separation between the allocation code and the cleanup code, and we don't repeat ourselves in the cleanup sections. cleanup_2
deallocates all the rows within a page, along with the page itself; cleanup_1
deallocates all of the pages. cleanup_2
"falls through" into cleanup_1
.
Here's a second method - first we allocate all a[i]
before allocating any a[i][j]
, then we make sure all a[i][j]
were successfully allocated before initializing the array contents.
/**
* Breadth-first approach; allocate all a[i], then all a[i][j], then initialize
*/
int ***alloc2( size_t pages, size_t height, size_t width )
{
size_t i, j, k;
/**
* Allocate space for N int ** objects, where N == pages
*/
int ***a = malloc( sizeof *a * pages );
if ( !a )
return NULL; // allocation failed for initial sequence, return NULL
for ( i = 0; i < pages; i++ ) // For each a[i], allocate N objects of type
{ // int *, where N == height
a[i] = malloc( sizeof *a[i] * height );
if ( !a[i] )
break;
}
if ( i < pages )
{
while ( i-- ) // allocation of a[i] failed, free up a[0] through a[i-1]
free( a[i] );
free( a ); // free a
return NULL;
}
for ( i = 0; i < pages; i++ )
{
for ( j = 0; j < height; j++ )
{
a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate
if ( !a[i][j] ) // space for N int objects,
break; // where N == width
}
}
if ( j < h )
{
do
{
while ( j-- ) // allocation of a[i][j] failed, free up a[i][0] through a[i][j-1]
free( a[i][j] ); // repeat for all of a[0] through a[i-1].
free( a[i] );
j = h;
} while ( i-- );
free( a ); // free a
return NULL;
}
/**
* All allocations were successful, initialize array contents
*/
for ( i = 0; i < pages; i++ )
for ( j = 0; j < height; j++ )
for ( k = 0; k < width; k++ )
a[i][j][k] = initial_value( i, j, k );
return a;
}
No goto
s! But the code doesn't "flow" as well IMO. There's not as clean a separation between allocation and cleanup code, and there's a bit of repetitiveness between the cleanup sections.
Note that for both methods, the memory is not contiguous - the element immediately following a[0][0][h-1]
is not going to be a[0][1][0]
. If you need all your array elements to be adjacent in memory, you will need to use the method shown by the others:
int (*a)[h][w] = malloc( sizeof *a * 3 );
with the caveat that if h
and w
are large, you may not have enough contiguously allocated memory to satsify the request.