Questo è un classico assoluto in ambito didattico. Questa versione contiene anche tutto il codice per la programmazione difensiva e le relative #define per la simulazione dei faults, che per il momento possono essere tranquillamente ignorate privilegiando la comprensione della logica di base, ma vanno comunque tesaurizzate per il futuro. 
/************************************************************************/
/** Scopo del programma:                                               **/
/** Allocazione didattica di array nested (multidimensionali)          **/
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ASIZE (8)
typedef enum {FALSE, TRUE} Bool_t;
#ifdef ALLOC_FAIL_02
  #ifdef ALLOC_FAIL_01
    #undef ALLOC_FAIL_01
  #endif
  #pragma message (">> TEST VERSION compiled with ALLOC_FAIL_02 defined")
#endif
#ifdef ALLOC_FAIL_01
  #pragma message (">> TEST VERSION compiled with ALLOC_FAIL_01 defined")
#endif
Bool_t matrix_alloc(int ***m, const unsigned int row, const unsigned int col)
{
    unsigned int i;
    assert(row > 0);
    assert(col > 0);
    /*
    ** PRIMO STEP: allocazione array di base
    */
#ifdef ALLOC_FAIL_01
    *m = NULL;
#else
    *m = (int **)malloc(row * sizeof(int *));
#endif
    if (NULL == *m)
    {
        fputs("Errore di allocazione base array !\n", stderr);
        return (FALSE);
    }
    for (i = 0; i < row; ++i)
    {
        int *p;
        /*
        ** SECONDO STEP: allocazione di ogni singolo array-riga
        */
        p = (int *)malloc(col *sizeof(int));
#ifdef ALLOC_FAIL_02
        if (row -1 == i)
        {
            free(p);
            p = NULL;
        }
#endif
        if (NULL != p)
        {
            /*
            ** TERZO e ULTIMO STEP: collegamento riga ad array base
            */
            (*m)[i] = p;
        }
        else
        {
            fprintf(stderr, "Errore di allocazione alla riga %d!\n"
                    "Deallocazione array: ", i);
            while (i--)
            {
                free((*m)[i]);
                fprintf(stderr, "%d ", i);
            }
            free(*m);
            return (FALSE);
        }
    }
    return (TRUE);
}
int main(void)
{
    int **array, righe, colonne;
    int i, j;
    Bool_t status;
    righe   = ASIZE;
    colonne = ASIZE;
    printf("Allocazione dinamica di un array "
           "bidimensionale %d x %d\n",
           righe, colonne);
    status = matrix_alloc(&array, righe, colonne);
    if(status)
    {
        printf("** Allocazione effettuata. "
               "Inizializzazione di %d celle in corso...\n",
               righe * colonne);
        for (i = 0; i < righe; ++i)
        {
            for (j = 0; j < colonne; ++j)
            {
                array[ i ][ j ] = i * 10 + j;
            }
        }
        puts("** Contenuto dell'array:");
        for (i = 0; i < righe; ++i)
        {
            for (j = 0; j < colonne; ++j)
            {
                printf( "array[%d][%d] = %02d\n", i, j, array[ i ][ j ]);
            }
            puts("");
            free(array[i]);
        }
        free(array);
    }
    return (EXIT_SUCCESS);
}/* EOF: alloc_array.c */
PS: quando presenti codice sorgente, devi usare gli appositi tag Code.