void FillMatrix( int** matrix, const int size, const unsigned randRange, const int shift ) {
int i, j;
for ( i = 0; i < size; ++i )
for ( j = 0; j < size; ++j )
// shift - начальное значение диапазона, randRange - диапазон значений
matrix[ i ][ j ] = shift + rand() % randRange;
}

возможно будет полезно с Си и С++
а ) -вот это ссылка на сам массив --- int** matrix --- экономиня ресурсов памяти и скорость
б ) - это копия на массив -- int arr[][] ---создание копии массива расход памяти и
снижение скорости из за копирования
Потому, что динамический массив указателей - это одномерный массив указателей на массивы. Указатель на указатель на int. И int arr[][] - это синоним int **arr.
Но если у тебя int q[n][m], то тип q - указатель на int[m]. И параметр в заголовке функции должен быть объявлен как int arr[][m]. Но если m - переменная, такая запись невозможна.
По факту же динамический массив - это просто непрерывный участок памяти (одномерный массив длиной m * n), к которому компилятор эмулирует обращение как к двумерному массиву. И при обращении к q[i][j] происходит неявное преобразование в обращение к: ((int*)q)[i * m + j].
Но это возможно только внутри функции, в которой объявлена q, т.к. за её пределами вся информация о размерах массива теряется. И тут либо указывать в заголовке функции явный константный размер: int arr[][5], либо передавать q в функцию как одномерный массив.
Можно и динамический массив не указателей. Просто размер в любом случае надо указывать отдельно, он прямо вместе с массивом не передается. Фактически передается только адрес начала массива и тип его элементов.
Массивы остаются там, где были созданы, дальнейшая работа с ними происходит по указателю. И если с динамическим массивом явно используется привычный указатель со звездочкой *, то статический для удобства обозначили [] квадратными скобками. И если для обычной записи мы указываем размер в отдельную переменную (хотя можем и не указывать вовсе), то
при записи типа a[] - абсолютно равноценно *a,
при a[][10] - та же самая *a, только компилятор сам подсматривает параметры статического массива и перегружает операторы индексации [x][y] в *(a + size_x * x + y);
size_x он берет из места, где мы обьявляли массив. Ну или как-то так, мы все тупее трупа страуса... и не поймем.
Ну, это более рационально. И такова архитектура процессора(ов). Указатель на память и доступ к ней - это одно и то же. Во всех языках высокого уровня, все объекты, хендлы, дискрипторы - на самом деле указатели.