There are a few subtle points that can make dynamically creating 2D matricies more robust and less likely to provide a chance for inadvertent read from an uninitialized value (undefined behavior). The No. 1 improvement you can make is to allocate the column arrays with calloc such that the memory for each cell is already initialized to zero - 0 at the time of allocation. This allows immediate iteration over the entire matrix without the potential for a read of an uninitialized value. Take a look at the following:
#include <stdio.h>
#include <stdlib.h>
int **mtrx_calloc (size_t m, size_t n);                /* initialize elements to 0  */
int **realloc_rows (int **ap, size_t *m, size_t n, size_t newm); /* resize newm x n */
void mtrx_prn (size_t m, size_t n, int **matrix);      /* print matrix with/pad     */
void mtrx_free (size_t m, int **matrix);               /* free memory allocated     */
int main (int argc, char **argv)
{
    /* set initial size from arguments given (default: 3 x 4) */
    size_t m = argc > 2 ? (size_t)atoi (argv[1]) : 3;
    size_t n = argc > 2 ? (size_t)atoi (argv[2]) : 4;
    /* allocate the m x n matrix */
    int **matrix = mtrx_calloc (m, n);
    /* fill with misc values */
    register size_t i = 0, j = 0;
    for (i = 0; i < m; i++)
    {
        for (j = 0; j < n; j++)
            matrix [i][j] = (int)(i + j);
    }
    /* print matrix */
    printf ("\nThe dynamically allocated %zux%zu matrix is:\n\n", m, n);
    mtrx_prn (m, n, matrix);
    /* reallocate matrix - add 4 rows */
    printf ("\nReallocate matrix to %zux%zu:\n\n", m + 4, n);
    size_t oldm = m;
    matrix = realloc_rows (matrix, &m, n, m + 4);
    /* fill new rows with misc values */
    for (i = oldm; i < m; i++)
    {
        for (j = 0; j < n; j++)
            matrix [i][j] = (int)(i + j);
    }
    mtrx_prn (m, n, matrix);
    /* free memory alocated */
    mtrx_free (m, matrix);
    /* just to make it look pretty */
    printf ("\n");
    return 0;
}
/* allocate/initialize mxn matrix */
int **mtrx_calloc (size_t m, size_t n)
{
    register size_t i;
    int **array = calloc (m, sizeof *array);
    if (!array) {   /* validate allocation  */
        fprintf (stderr, "%s() error: memory allocation failed.\n", __func__);
        exit (EXIT_FAILURE);
    }
    for (i = 0; i < m; i++)
    {
        array[i] = calloc (n, sizeof **array);
        if (!array[i]) {   /* validate allocation  */
            fprintf (stderr, "%s() error: memory allocation failed.\n", __func__);
            exit (EXIT_FAILURE);
        }
    }
    return array;
}
/* realloc an array of pointers to int* setting memory to 0. */
int **realloc_rows (int **ap, size_t *m, size_t n, size_t newm)
{
    if (newm <= *m) return ap;
    size_t i = 0;
    int **tmp = realloc (ap, newm * sizeof *ap);
    if (!tmp) {
        fprintf (stderr, "%s() error: memory reallocation failure.\n", __func__);
        // return NULL;
        exit (EXIT_FAILURE);
    }
    ap = tmp;
    for (i = *m; i < newm; i++)
    {
        ap[i] = calloc (n, sizeof **ap);
        if (!ap[i]) {   /* validate allocation  */
            fprintf (stderr, "%s() error: memory allocation failed.\n", __func__);
            exit (EXIT_FAILURE);
        }
    }
    *m = newm;
    return ap;
}
/* print a (m x n) matrix (check pad alloc) */
void mtrx_prn (size_t m, size_t n, int **matrix)
{
    register size_t i, j;
    for (i = 0; i < m; i++)
    {
        char *format = "[ %2d";
        for (j = 0; j < n; j++)
        {
            printf (format, matrix [i][j]);
            format = ", %2d";
        }
        puts(" ]");
    }
}
void mtrx_free (size_t m, int **matrix)
{
    register size_t i;
    for (i = 0; i < m; i++)
    {
        free (matrix [i]);
    }
    free (matrix);
}
**Create 5x4 matrix and reallocate to **
$ ./bin/mtrx_dyn_int 4 5
The dynamically allocated 4x5 matrix is:
[  0,  1,  2,  3,  4 ]
[  1,  2,  3,  4,  5 ]
[  2,  3,  4,  5,  6 ]
[  3,  4,  5,  6,  7 ]
Reallocate matrix to 8x5:
[  0,  1,  2,  3,  4 ]
[  1,  2,  3,  4,  5 ]
[  2,  3,  4,  5,  6 ]
[  3,  4,  5,  6,  7 ]
[  4,  5,  6,  7,  8 ]
[  5,  6,  7,  8,  9 ]
[  6,  7,  8,  9, 10 ]
[  7,  8,  9, 10, 11 ]
Check Memory Errors/Leaks
$ valgrind ./bin/mtrx_dyn_int 4 5
==31604== Memcheck, a memory error detector
==31604== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==31604== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==31604== Command: ./bin/mtrx_dyn_int 4 5
==31604==
The dynamically allocated 4x5 matrix is:
[  0,  1,  2,  3,  4 ]
[  1,  2,  3,  4,  5 ]
[  2,  3,  4,  5,  6 ]
[  3,  4,  5,  6,  7 ]
Reallocate matrix to 8x5:
[  0,  1,  2,  3,  4 ]
[  1,  2,  3,  4,  5 ]
[  2,  3,  4,  5,  6 ]
[  3,  4,  5,  6,  7 ]
[  4,  5,  6,  7,  8 ]
[  5,  6,  7,  8,  9 ]
[  6,  7,  8,  9, 10 ]
[  7,  8,  9, 10, 11 ]
==31604==
==31604== HEAP SUMMARY:
==31604==     in use at exit: 0 bytes in 0 blocks
==31604==   total heap usage: 10 allocs, 10 frees, 256 bytes allocated
==31604==
==31604== All heap blocks were freed -- no leaks are possible
==31604==
==31604== For counts of detected and suppressed errors, rerun with: -v
==31604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
Matrix Allocated In Single Block - Stride Determines Dimensions
Here is another example that creates a single dimension array that is interpreted as a 2D array by setting a stride defining the number of elements to be considered in each row/col. This approach provides an easier way to handle the underlying array data in a 1D array, but the logic to simulate a 2D array grows more complex to accommodate the underlying 1D array. You are free to remove the label data from the struct that holds the size & stride information, I found it convenient when working with multiple stride settings. Here is the example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _STDINT_H
    typedef unsigned char uchar;
    typedef unsigned int  uint;
    typedef unsigned long ulong;
#endif
/** struct mdata defines matrix metadata for size, stride, label and lblsz.
*
*  struct mdata defines metadata for handling array of numbers as 2d array.
*/
typedef struct mdata
{
    int size;
    int stride;
    char *label;
    size_t lblsz;
} mdata;
/* function prototypes */
void mtrx_prnmatrix (int *m, mdata *md);
void mtrx_prnrow (int *m, mdata *md, int v);
void mtrx_prncol (int *m, mdata *md, int v);
void mtrx_showmdata (mdata *md);
long mtrx_getval (int *m, mdata *md, int x, int y);
long mtrx_setval (int *m, mdata *md, int x, int y, int val);
int mtrx_setlable (mdata *md, char *s);
int mtrx_setstride (mdata *md, int stride);
void free_mtrxmd (mdata *md);
int main (int argc, char **argv) {
    /* static for testing, you can allocate this single block */
    int mtrx[] = { 1, 2, 3, 4, 5, 6,
                7, 8, 9, 10, 11, 12,
                13, 14, 15, 16, 17, 18,
                19, 20, 21, 22, 23, 24,
                25, 26, 27, 28, 29, 30,
                31, 32, 33, 34, 35, 36 };
    int sz = 36;
    int stride = 6;
    int vreq = 0;
    mdata *m1d;
    m1d = malloc (sizeof *m1d);
    m1d-> size = sz;
    m1d-> stride = stride;
    m1d-> label = strdup ("m1d (6x6)");
    m1d-> lblsz = strlen (m1d-> label);
    if (argc < 2 ) {
        fprintf (stderr, "Error: insufficient input, usage: %s int (vector [0-5])\n", argv[0]);
        return 1;
    }
    /* show the metadata */
    mtrx_showmdata (m1d);
    /* set the vector request - use strtol for error check */
    vreq = atoi (argv[1]);
    /* print the full matrix */
    mtrx_prnmatrix (mtrx, m1d);
    printf ("\n");
    /* print the requested column vector */
    mtrx_prncol (mtrx, m1d, vreq);
    /* print the requested row vector */
    mtrx_prnrow (mtrx, m1d, vreq);
    /* set a new stride for matrix (set new temp label) */
    mtrx_setstride (m1d, 4);
    mtrx_showmdata (m1d);
    /* set a new label for matrix */
    mtrx_setlable (m1d, "m1d (9x4)");
    mtrx_showmdata (m1d);
    /* print the full updated matrix */
    mtrx_prnmatrix (mtrx, m1d);
    printf ("\n");
    /* set a new stride and label for matrix */
    mtrx_setstride (m1d, 3);
    mtrx_setlable (m1d, "m1d (12x3)");
    mtrx_prnmatrix (mtrx, m1d);
    printf ("\n");
    /* set a new stride and label for matrix */
    mtrx_setstride (m1d, 2);
    mtrx_setlable (m1d, "m1d (18x2)");
    mtrx_prnmatrix (mtrx, m1d);
    printf ("\n");
    /* mtrx_getval test */
    mtrx_showmdata (m1d);
    mtrx_setval (mtrx, m1d, 9, 1, 99);
    int i = 0;
    for (i = 0; i < (m1d-> size / m1d-> stride); i++) {
        printf (" mtrx [%2d,%2d] : %2ld\n", i, 0, mtrx_getval (mtrx, m1d, i, 0));
        printf (" mtrx [%2d,%2d] : %2ld\n", i, 1, mtrx_getval (mtrx, m1d, i, 1));
    }
    printf ("\n");
    /* free data allocated to metadata */
    free_mtrxmd (m1d);
    return 0;
}
/** mtrx_prnmatrix (int *, int, mdata *) print matrix in row x column format.
*
*  mtrx_prnmatrix print matrix in row x column format with metadata label.
*/
void mtrx_prnmatrix (int *m, mdata *md)
{
    int i = 0;
    if (!md) {
        fprintf (stderr, "error: metadata structure not initialized\n");
    }
    printf ("Matrix: %s\n", md->label);
    for (i = 0; i < md-> size; i++)
        if (((i + 1) % md-> stride) == 0)
            if (i == (md->size - 1))
                printf (" %2d ]\n", m[i]);
            else
                printf (" %2d\n", m[i]);
        else
            if (i == 0)
                printf ("[%2d", m[i]);
            else
                printf (" %2d", m[i]);
}
/** mtrx_prnrow (int *, mdata *, int) prints row vector.
*
*  mtrx_prnrow prints matrix row vector based on metadata.
*/
void mtrx_prnrow (int *m, mdata *md, int v)
{
    register int it = v;
    if (!md) {
        fprintf (stderr, "error: metadata structure not initialized\n");
    }
    if (v > md-> size/md-> stride - 1 || v < 0) {
        fprintf (stderr, "error: invalid rvector (%d), valid: 0 < rvector < max (%d)\n",
                v, md-> size/md-> stride);
        return;
    }
    if (md-> label) printf ("Matrix: %s -- row vector: %d\n", md-> label, v);
    for (it = v * md-> stride; it < (v * md-> stride) + md-> stride; it++)
        printf (" %d", m[it]);
    printf ("\n");
}
/** mtrx_prncol (int *, mdata *, int) prints column vector.
*
*  mtrx_prncol prints matrix column vector based on metadata.
*/
void mtrx_prncol (int *m, mdata *md, int v)
{
    register int it = v;
    if (!md) {
        fprintf (stderr, "error: metadata structure not initialized\n");
    }
    if (v > md-> size/md-> stride - 1 || v < 0) {
        fprintf (stderr, "error: invalid vector (%d), valid: 0 < vector < max (%d)\n",
                v, md-> size/md-> stride);
        return;
    }
    if (md-> label) printf ("Matrix: %s -- column vector: %d\n", md-> label, v);
    for (it = v; it < md-> size; it += md-> stride)
        printf (" %d\n", m[it]);
}
/** mtrx_showmdata (mdata *) prints metadata struct.
*
*  mtrx_showmdata prints label, size, stride and lblsz metadata.
*/
void mtrx_showmdata (mdata *md)
{
    printf ("\n label : %s\n size  : %d\n stride: %d\n lblsz : %zd\n\n",
            md-> label, md-> size, md-> stride, md-> lblsz);
}
/** mtrx_getval (int *, mdata *, int, int, int) retrieves value at position x,y).
*
*  mtrx_getval gets the value at the give position within the matrix based on x, y indexes.
*/
long mtrx_getval (int *m, mdata *md, int x, int y)
{
    if (x * y > md-> size) {
        fprintf (stderr, "%s()  error: invalid index, (x * y) > size.\n", __func__);
        return -1;
    }
    if (x > (md-> size / md-> stride - 1)) {
        fprintf (stderr, "%s()  warning: invalid metadata index, (x > %d).\n",
                __func__, md-> size/md-> stride - 1);
    }
    if (y > (md-> stride - 1)) {
        fprintf (stderr, "%s()  warning: invalid metadata index, (y > %d).\n", __func__, md-> stride - 1);
    }
    return m[(x * md-> stride) + y];
}
/** mtrx_setval (int *, mdata *, int, int, int) sets value at position x,y).
*
*  mtrx_setval set the value at the give position within the matrix based on x, y indexes.
*/
long mtrx_setval (int *m, mdata *md, int x, int y, int val)
{
    if (x * y > md-> size) {
        fprintf (stderr, "%s()  error: invalid index, (x * y) > size.\n", __func__);
        return -1;
    }
    if (x > (md-> size / md-> stride - 1)) {
        fprintf (stderr, "%s()  warning: invalid metadata index, (x > %d).\n",
                __func__, md-> size/md-> stride - 1);
    }
    if (y > (md-> stride - 1)) {
        fprintf (stderr, "%s()  warning: invalid metadata index, (y > %d).\n", __func__, md-> stride - 1);
    }
    return m[(x * md-> stride) + y] = val;
}
/** mtrx_setlable (mdata *, char *) sets new label in metadata struct.
*
*  mtrx_setlable sets new label metadata and updates lblsz.
*/
int mtrx_setlable (mdata *md, char *s)
{
    if (!md) {
        fprintf (stderr, "%s()  error: metadata structure not initialized\n", __func__);
        if (!(md = malloc (sizeof (md)))) {
            fprintf (stderr, "%s()  metadata structure allocation failed \n", __func__);
            return 0;
        }
    }
    if (!s) {
        fprintf (stderr, "%s()  error: string not initialized\n", __func__);
        return 0;
    }
    md-> lblsz = strlen (s);
    char *tmp = realloc (md-> label, md-> lblsz + 1);
    if (!tmp) {
        fprintf (stderr, "%s()  error: metadata - label realloc failed.\n", __func__);
        return 0;
    }
    strncpy (tmp, s, md-> lblsz + 1);
    md-> label = tmp;
    return 1;
}
/** mtrx_setstride (mdata *, int) sets new stride in metadata struct.
*
*  mtrx_setstride validates and sets new stride metadata with temp label.
*/
int mtrx_setstride (mdata *md, int stride)
{
    char newlabel[256];
    int newrows = 0;
    if (!md) {
        fprintf (stderr, "%s()  error: metadata structure not initialized\n", __func__);
        md = malloc (sizeof (md));
        if (!md)
            fprintf (stderr, "%s()  metadata structure allocated\n", __func__);
        else {
            fprintf (stderr, "%s()  metadata structure allocation failed \n", __func__);
            return 0;
        }
    }
    if (stride < 1) {
        fprintf (stderr, "%s()  error: invalid (stride < 1) supplied.\n", __func__);
        return 0;
    }
    if (md-> size % stride) {
        fprintf (stderr, "%s()  error: invalid stride (size %% stride != 0)\n", __func__);
        return 0;
    }
    md-> stride = stride;
    newrows = md-> size / stride;
    sprintf (newlabel, "%s -> now (%dx%d)", md->label, newrows, stride);
    mtrx_setlable (md, newlabel);
    return 1;
}
void free_mtrxmd (mdata *md)
{
    if (md-> label) free (md-> label);
    if (md) free (md);
}
Output
$ /bin/mtrx_metadata_new 4
 label : m1d (6x6)
 size  : 36
 stride: 6
 lblsz : 9
Matrix: m1d (6x6)
[ 1  2  3  4  5  6
  7  8  9 10 11 12
 13 14 15 16 17 18
 19 20 21 22 23 24
 25 26 27 28 29 30
 31 32 33 34 35 36 ]
Matrix: m1d (6x6) -- column vector: 4
 5
 11
 17
 23
 29
 35
Matrix: m1d (6x6) -- row vector: 4
 25 26 27 28 29 30
 label : m1d (6x6) -> now (9x4)
 size  : 36
 stride: 4
 lblsz : 22
 label : m1d (9x4)
 size  : 36
 stride: 4
 lblsz : 9
Matrix: m1d (9x4)
[ 1  2  3  4
  5  6  7  8
  9 10 11 12
 13 14 15 16
 17 18 19 20
 21 22 23 24
 25 26 27 28
 29 30 31 32
 33 34 35 36 ]
Matrix: m1d (12x3)
[ 1  2  3
  4  5  6
  7  8  9
 10 11 12
 13 14 15
 16 17 18
 19 20 21
 22 23 24
 25 26 27
 28 29 30
 31 32 33
 34 35 36 ]
Matrix: m1d (18x2)
[ 1  2
  3  4
  5  6
  7  8
  9 10
 11 12
 13 14
 15 16
 17 18
 19 20
 21 22
 23 24
 25 26
 27 28
 29 30
 31 32
 33 34
 35 36 ]
 label : m1d (18x2)
 size  : 36
 stride: 2
 lblsz : 10
 mtrx [ 0, 0] :  1
 mtrx [ 0, 1] :  2
 mtrx [ 1, 0] :  3
 mtrx [ 1, 1] :  4
 mtrx [ 2, 0] :  5
 mtrx [ 2, 1] :  6
 mtrx [ 3, 0] :  7
 mtrx [ 3, 1] :  8
 mtrx [ 4, 0] :  9
 mtrx [ 4, 1] : 10
 mtrx [ 5, 0] : 11
 mtrx [ 5, 1] : 12
 mtrx [ 6, 0] : 13
 mtrx [ 6, 1] : 14
 mtrx [ 7, 0] : 15
 mtrx [ 7, 1] : 16
 mtrx [ 8, 0] : 17
 mtrx [ 8, 1] : 18
 mtrx [ 9, 0] : 19
 mtrx [ 9, 1] : 99
 mtrx [10, 0] : 21
 mtrx [10, 1] : 22
 mtrx [11, 0] : 23
 mtrx [11, 1] : 24
 mtrx [12, 0] : 25
 mtrx [12, 1] : 26
 mtrx [13, 0] : 27
 mtrx [13, 1] : 28
 mtrx [14, 0] : 29
 mtrx [14, 1] : 30
 mtrx [15, 0] : 31
 mtrx [15, 1] : 32
 mtrx [16, 0] : 33
 mtrx [16, 1] : 34
 mtrx [17, 0] : 35
 mtrx [17, 1] : 36