This demonstrates VLAs (variable length arrays) at work.  It is based on code for SO 32565694, which asked about VLAs in structures.  This code avoids using structures.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
    int nr, nc;
    void *data;     // Actually double a[nr][nc]
} Matrix;
static double variant1(int nr, int nc, int r, int c)
{
    assert(nr != 0);
    return (r * nc) + c + 1;
}
static double variant2(int nr, int nc, int r, int c)
{
    return ((nr - r) * nc) + (nc - c) + 1;
}
typedef double (*Initializer)(int nr, int nc, int r, int c);
static void mat_init(int nr, int nc, double m[nr][nc], Initializer init)
{
    assert(m != 0 && nr > 0 && nc > 0);
    printf("Set: [%dx%d]\n", nr, nc);
    for (int i = 0; i < nr; i++)
    {
        printf("[%d]:", i);
        for (int j = 0; j < nc; j++)
        {
            double v = init(nr, nc, i, j);
            m[i][j] = v;
            printf(" %6.1f", v);
        }
        putchar('\n');
    }
}
static void mat_dump(const char *tag, int nr, int nc, double m[nr][nc])
{
    assert(m != 0 && nr > 0 && nc > 0);
    printf("Matrix %s: %dx%d\n", tag, nr, nc);
    for (int i = 0; i < nr; i++)
    {
        printf("[%d]:", i);
        for (int j = 0; j < nc; j++)
            printf(" %6.1f", m[i][j]);
        putchar('\n');
    }
}
static void mat_multiply(int r1, int c1, double m1[r1][c1],
                         int r2, int c2, double m2[r2][c2],
                         int r3, int c3, double m3[r3][c3])
{
    assert(r1 > 0 && c1 > 0 && r2 > 0 && c2 > 0 && r3 > 0 && c3 > 0);
    printf("m1[%d][%d] x m2[%d][%d] = m3[%d][%d]\n", r1, c1, r2, c2, r3, c3);
    assert(r1 == r3 && c2 == c3 && c1 == r2);
    for (int i = 0; i < r1; i++)
    {
        for (int j = 0; j < c2; j++)
        {
            double sum = 0.0;
            for (int k = 0; k < c1; k++)
                sum += m1[i][k] * m2[k][j];
            m3[i][j] = sum;
        }
    }
}
int main(void)
{
    int r1 = 3;
    int c1 = 5;
    int r2 = c1;
    int c2 = 4;
    int r3 = r1;
    int c3 = c2;
    double m1[r1][c1];
    double m2[r2][c2];
    double m3[r3][c3];
    printf("m1:\n");
    mat_init(r1, c1, m1, variant1);
    printf("m2:\n");
    mat_init(r2, c2, m2, variant2);
    mat_dump("m1", r1, c1, m1);
    mat_dump("m2", r2, c2, m2);
    mat_multiply(r1, c1, m1, r2, c2, m2, r3, c3, m3);
    mat_dump("m3", r3, c3, m3);
    return 0;
}
Example output:
m1:
Set: [3x5]
[0]:    1.0    2.0    3.0    4.0    5.0
[1]:    6.0    7.0    8.0    9.0   10.0
[2]:   11.0   12.0   13.0   14.0   15.0
m2:
Set: [5x4]
[0]:   25.0   24.0   23.0   22.0
[1]:   21.0   20.0   19.0   18.0
[2]:   17.0   16.0   15.0   14.0
[3]:   13.0   12.0   11.0   10.0
[4]:    9.0    8.0    7.0    6.0
Matrix m1: 3x5
[0]:    1.0    2.0    3.0    4.0    5.0
[1]:    6.0    7.0    8.0    9.0   10.0
[2]:   11.0   12.0   13.0   14.0   15.0
Matrix m2: 5x4
[0]:   25.0   24.0   23.0   22.0
[1]:   21.0   20.0   19.0   18.0
[2]:   17.0   16.0   15.0   14.0
[3]:   13.0   12.0   11.0   10.0
[4]:    9.0    8.0    7.0    6.0
m1[3][5] x m2[5][4] = m3[3][4]
Matrix m3: 3x4
[0]:  215.0  200.0  185.0  170.0
[1]:  640.0  600.0  560.0  520.0
[2]: 1065.0 1000.0  935.0  870.0
It would be perfectly feasible to use command line arguments or user input to set the values of r1, c1 (or r2) and c2 (but the values in r2 (or c1), r3 and c3 are constrained, for this code, by r1, c1 and c2).  The initialization code would work fine, and so would the multiplication.