Much of this has already been explained in comments to your question.
Your definition of display
:
void display(int*p[N_ROWS][N_COLS])
{
This says p
will be an array N_ROWS
of array N_COLS
of pointers to int
.
When you call display
:
int Array [N_ROWS][N_COLS] = { {1,2,3},{4,5,6},{7,8,9} };
display(&Array);
You are passing in the address of Array
, thus it is a pointer to an array N_ROWS
of array N_COLS
of int
.
To make the definition of display
match the way you are calling it:
void display(int (*p)[N_ROWS][N_COLS])
{
The parentheses make the *
associate with p
first. Since p
is a pointer to an array of array of int
, getting to the int
requires an extra dereference:
printf("%i\n", (*p)[i][j]);
Defining display
to take a pointer to an array means that the size of the array is bound to the type parameter, and thus display
knows exactly the dimensions of the array.
An alternative means would be to define display
to take the dimension of the array as a second parameter.
void display(int p[][N_COLS], int n_rows)
{
In this case the p
parameter is a pointer to an array N_COLS
of int
. An array of T when used in most expressions will decay to a value of type pointer to T equal to the address of its first element. Thus, you could call this second version of display
like this:
int Array [N_ROWS][N_COLS] = { {1,2,3},{4,5,6},{7,8,9} };
display(Array, N_ROWS);
The advantage of this second approach is that display
can work with arrays that have fewer or more than N_ROWS
. The disadvantage is that it is up to the caller to pass in the right number of rows.
You might think that the following declaration would give you complete type safety:
void display(int p[N_ROWS][N_COLS])
{
But, the array syntax in C for function parameters cause the size information for p
to be ignored, and becomes equivalent to int p[][N_COLS]
, which in turn is treated as int (*p)[N_COLS]
.