You actually have only one array of ints (i.e. int arr1[][5]
) and one pointer to a pointer of an int, i.e. int **arr2
. Even if an array like arr1[10][5]
, when passed as argument to a function, decays to a pointer to the beginning of the memory where the elements reside, there is a (big) difference in memory layout and in the way how a compiler treats access to these pointers.
BTW, in main it should be int n=5,m=4;int arr1[m][n]
, not int n=5,m=4;int arr1[n][m]
.
Concerning memory layout:
A 2D integer array of the form int [10][5]
is represented as 10 consecutive "rows", each comprising 5 "columns" (i.e. integral values). The size of this array is 10 * 5 * sizeof(int)
, and the size of one "row" is 5 * sizeof(int)
.
A pointer to a pointer to int int **p
is just a single pointer; its size is sizeof(int**)
, even if you have "malloced" a sequence of integral pointers lile p = malloc(10 * sizeof (int*))
; Note "*" in sizeof(int *)
, as you create a sequence of pointers to integers, not a sequence of integers. That's the main difference in memory layout: It's not a 2D array of integers, but a 1D array of pointers to integers. And if one actually had allocated 10 "rows" for "10" integers, each row could be located in a different portion of memory. The space needed for managing such a (spreaded) amount of 10x5 integral values is "10*sizeof(int*) + 10*5*sizeof(int)".
Concerning access:
Let's assume a variable of type int arr[][5]
, which is a 2D-array of integers, where the size of a column is 5
and the number of rows is not determined. Informally, an access like int x = arr[3][4]
is translated into an access the (3*5 + 4)
th element of the array, i.e. "row times rowsize plus column"; Note that - based on this formula - the compiler does not need to know how many rows the the array actually has.
In contrast, let's assume a variable of type int **p
. You can think of an access like x = p[3][4]
as being equivalent to int *r = p[3]; int x = r[4]
; Note that r
is of type int *
, i.e. it is a pointer, and r[4]
then dereferences this pointer and returns an integral value.
This is rather informally described.
Yet the main issue is that the memory layout of an arr[][5]
contains consecutive integral values only, whereas int **arrr
may be a seqence of pointers (or even just one such pointer), each of them probably pointing to a sequence of integral values (or just one integral value).