Давайте визуализировать то, что вы пытаетесь сделать первый, а затем я покажу код, чтобы добраться:
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
вызова, и если есть ошибка, вы хотите, чтобы очистить все ранее выделенную память до выручая, в противном случае вы рискуете утечки памяти. К сожалению, нет действительно чистого или элегантного способа сделать это - вы либо несете флаг и делаете кучу дополнительных тестов, либо бросаете пару goto
с. Я собираюсь показать два разных подхода, ни один из которых не хорош.
Первый метод - как мы выделяем каждый 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.
}
Да, этот код содержит goto
с, и это нарушает одно правил моего питомца (никогда не отступайте назад). Но между кодом распределения и кодом очистки существует довольно четкое разделение, и мы не повторяемся в разделах очистки. cleanup_2
освобождает все строки внутри страницы вместе с самой страницей; cleanup_1
освобождает все страницы. cleanup_2
"проваливается" в cleanup_1
.
Вот второй метод - сначала выделить всеa[i]
перед выделением каких-либо a[i][j]
, то убедитесь, что все 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;
}
No goto
s! Но код не «течет», а ИМО. Существует не так чистое разделение между кодом выделения и очистки, и между разделами очистки есть немного повторяемости.
Обратите внимание, что для обоих методов, память не смежный - элемент сразу после a[0][0][h-1]
не будет a[0][1][0]
. Если вам нужны все ваши элементы массива, чтобы быть смежными в памяти, вам нужно будет использовать метод, проявленный другие:
int (*a)[h][w] = malloc(sizeof *a * 3);
с оговоркой, что если h
и w
большие, вы не можете иметь достаточно смежно выделены памяти для запроса запроса.
'int (* a) [height] [width] = malloc (3 * sizeof * a);' должен предоставить вам 3D-массив. – mch
не создаст ли указатель на 2D-массив высот, ширины? –
yes, 'a' является указателем на 2d-массив и может использоваться с' a [i] [j] [k] '. – mch