让我们先想象一下你想要做什么,然后我会展示代码来实现:
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] | |
+---+ +---+
类型:
- 每个
a[i][j][k] 都有int 类型;
- 每个
a[i][j]指向int对象序列的第一个元素,所以它必须有int *类型;
- 每个
a[i]指向int *对象序列的第一个元素,所以它必须有int **类型;
-
a 指向int ** 对象序列的第一个元素,因此它的类型必须为int ***。
由于您正在进行零碎的嵌套分配,因此您需要检查每个malloc 调用的结果,如果出现错误,您需要在退出之前清理任何先前分配的内存,否则您会冒内存风险泄漏。不幸的是,没有真正干净或优雅的方法可以做到这一点 - 你要么随身携带一面旗帜并进行大量额外测试,要么投入几个gotos。我将展示两种不同的方法,它们都不是那么漂亮。
第一种方法 - 当我们分配每个 a[i] 时,我们也分配每个 a[i][j](“深度优先”方法)并初始化每个 a[i][j][k]。如果a[i][j] 上的分配失败,我们必须释放所有a[i][0] 到a[i][j-1],然后释放a[i],然后对a[0] 到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.
}
是的,这段代码包含gotos,它违反了我的一条宠物规则(永远不要向后分支)。但是,分配代码和清理代码之间有一个相当清晰的分离,我们不会在清理部分重复我们自己。 cleanup_2 释放页面内的所有行以及页面本身; cleanup_1 释放所有页面。 cleanup_2“落入”cleanup_1。
这是第二种方法——首先我们在分配任何a[i][j]之前分配所有a[i],然后我们确保在初始化数组内容之前成功分配所有a[i][j]。
/**
* 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;
}
没有gotos!但是代码并没有像 IMO 一样“流动”。分配代码和清理代码之间没有那么清晰的分离,清理部分之间有一些重复。
请注意,对于这两种方法,内存不是连续的 - 紧跟在 a[0][0][h-1] 之后的元素不会是 a[0][1][0]。如果您需要所有数组元素在内存中相邻,则需要使用其他显示的方法:
int (*a)[h][w] = malloc( sizeof *a * 3 );
需要注意的是,如果 h 和 w 很大,您可能没有足够的连续分配内存来满足请求。