#define ROWS 5
    #define COLS 10
    int multi[ROWS][COLS];
permite acceder a los elementos del array multi
mediante:
    multi[row][col]
o mediante:
    *(*(multi + row) + col)
Para entenderlo mejor, cambiemos
    *(multi + row)
por  X como en:
    *(X + col)
Ahora vemos que X es como un puntero porque la expresión
es desreferenciada y sabemos que col es un entero.
Aquí estamos utilizando "aritmética de punteros", y
al tratarse de un array de enteros, la dirección donde apunta
(por ej. el valor de) X + col + 1 debe ser mayor que la
dirección X + col por una diferencia de sizeof(int).
Ya sabemos como se guarda en memoria un array de dos dimensiones, y por tanto podemos determinar que en la expresión multi + row anterior, multi + row + 1 debe incrementarse por el valor necesario para "apuntar" a la siguiente línea, que en este caso es COLS * sizeof(int).
Esto quiere decir que para la evaluación correcta en tiempo de ejecución de la expresión *(*(multi + row) + col) el compilador debe generar el código teniendo en cuenta el valor de COLS (la segunda dimensión). Tanto la versión con punteros como la versión con arrays multi[row][col] son equivalentes.
Por tanto, para evaluar la expresión dada, se necesitan 5 valores:
    void set_value(int m_array[][COLS])
    {
        int row, col;
        for (row = 0; row < ROWS; row++)
        {
            for (col = 0; col < COLS; col++)
            {
                m_array[row][col] = 1;
            }
        }
    }
Y para llamar a esta función, podemos utilizar:
    set_value(multi);
Ahora, dentro de la función hemos utilizado los valores de ROWS y
COLS definidos al principio con #define ROWS 5 y #define COLS 10 
para establecer los límites de los bucles for. Para el compilador
estas definiciones son constantes. Tanto row como col
son variables locales. La definición formal de los parámetros
permiten al compilador determinar las características del valor
del puntero que será pasado en tiempo de ejecución. Realmente no
necesitamos la primera dimensión, y como veremos después, hay
momentos donde es preferible no definirlo dentro de la definición
de parámetros (por hábito o por consistencia, yo no lo he utilizado
aquí). Ahora, la segunda dimensión debe utilizarse como vemos en
la expresión del parámetro. La razón es que lo necesitamos en la
evaluación de m_array[row][col] como hemos visto.
Como el parámetro define el tipo de dato (en este caso int),
y las variables automáticas (row y col) se definen dentro de la
función para los bucles for, con un único parámetro sólo puede
pasarse un valor. En este caso, éste parámetro es el valor de 
multi como vemos en la llamada a la función, que es la 
dirección del primer elemento, a menudo también llamado como 
un puntero a un array. Por tanto, la única forma que tenemos de 
indicar la segunda dimensión al compilador es incluirlo 
explícitamente en la definición del parámetro.
De hecho, generalmente todas las dimensiones superiores a una son necesarias en arrays multidimensionales. Esto es: si hablamos de 3 dimensiones, la dimensión 2 y 3 deben especificarse en la definición de parámetros.