В памяти все элементы массива записываются по порядку, то есть последовательно. Двумерный массив можно рассматривать, как массив массивов, тогда станет понятно, что в памяти они записываются строка за строкой.
Предположим, у нас в памяти записан массив
int arr[m][n];, но известен только адрес первого элемента -
p = &arr[0][0];, и нам нужно получить доступ к элементу
a[i][j] используя указатель
p.
Сначала нам нужно сместится на i строк, то есть i*n элементов, а потом ещё на j элементов в текущей строке. Тогда искомый элемент -
p[i*n + j].
Как видите, не зная длинны строки
n мы не сможем получить доступ к элементу только по указателю и двум индексам, с другой стороны, количество строк
m не влияет на результат, поэтому его можно опустить.
По аналогии всё происходит и с массивами большей размерности: нам нужно знать все величины кроме первой.
P. S. Можно добиться и другого поведения, только понадобится выделять память динамически, а после освобождать её.
void foo(int** arr)
{
/* do something */
}
...
int** array = new int[m];
for (size_t i = 0; i < m; ++i)
{
array[i] = new int[n];
}
foo(arr);
for (size_t i = 0; i < m; ++i)
{
delete[] array[i];
}
delete[] array;