- Allocate an array to store pointers to arrays of row pointers.
- Allocate arrays to store row pointers.
- Allocate arrays to store each rows.
#include <stdlib.h>
int main(void) {
    int size1 = 2, size2 = 2, size3 = 2;
    int*** array;
    array = malloc(sizeof(int**) * size1); // 1
    for (int i = 0; i < size1; i++) {
        array[i] = malloc(sizeof(int*) * size2); // 2
        for (int j = 0; j < size2; j++) {
            array[i][j] = malloc(sizeof(int) * size3); // 3
        }
    }
    // free arrays
    for (int i = 0; i < size1; i++) {
        for (int j = 0; j < size2; j++) {
            free(array[i][j]);
        }
        free(array[i]);
    }
    free(array);
    return 0;
}
I wrote type names explicitly in the above example, but I suggest using dereferencing to obtain size of elements to allocate is better to prevent causing typos.
#include <stdlib.h>
int main(void) {
    int size1 = 2, size2 = 2, size3 = 2;
    int*** array;
    // allocate arrays
    array = malloc(sizeof(*array) * size1); // 1
    for (int i = 0; i < size1; i++) {
        array[i] = malloc(sizeof(*array[i]) * size2); // 2
        for (int j = 0; j < size2; j++) {
            array[i][j] = malloc(sizeof(*array[i][j]) * size3); // 3
        }
    }
    // free arrays
    for (int i = 0; i < size1; i++) {
        for (int j = 0; j < size2; j++) {
            free(array[i][j]);
        }
        free(array[i]);
    }
    free(array);
    return 0;
}
Onitted in the above examples to make them simple, but you should check results of malloc() to see if the allocations are successful.