Let’s start with:
int arr[][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
};
print(arr,3,4);
In print(arr,3,4);
, arr
is an array. Specifically, it is an array of 3 elements, each of which is an array of 4 elements, each of which is an int
. Thus, arr
is an array of 3 arrays of 4 int
. You have probably heard or read that arrays “decay” to pointers. This is a colloquial term. The actual rule, which you can find in clause 6.3.2.1, paragraph 3, of the C 2011 standard, is:
Except when it is the operand of the sizeof
operator, the _Alignof
operator, or the unary &
operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue.
Here is how this rule applies to arr
in print(arr,3,4);
:
arr
is an identifier, meaning it is the name of some object. As such, it designates its object. That object is an array of 3 arrays of 4 int
.
- This array is not the operand of
sizeof
or Alignof
or &
, and it is not a string literal. So, following the rule, it is converted from an array of 3 arrays of 4 int
to a pointer to the first array of 4 int
.
What happens next? Nothing. The expression we have is a pointer to an array of 4 int
. There is no rule that says a pointer to an array is converted to a pointer to a pointer. We have a pointer to an array, but that array is not used in an expression yet, not in the simple expression arr
. So it is not converted.
This means what you are passing to print
is a pointer to an array of 4 int
. But your declaration for print
says it takes a pointer to a pointer to an int
. Those are different things, and they are incompatible, so the compiler warns you.
(To see they are incompatible, consider the difference to a pointer to an array of 4 int
and a pointer to a pointer to int
. The memory at a pointer to an array of 4 int
contains 4 int
values. The memory at a pointer to a pointer to an int
contains a pointer. These are very different things.)
Next, consider:
void print (int **A, int m, int n)
…
printf("%d ", *((A+(m * 4) + n)));
We know from above that you ought to change int **A
to int (*A)[4]
, which is a pointer to an array of 4 int
. You can also change it to int A[][4]
, because there is a rule in C that such a parameter declaration will be automatically adjusted to be int (*A)[4]
, as a convenience. However, suppose you keep it as int **A
. Then does *((A+(m * 4) + n))
mean?
Since A
is a pointer to a pointer to an int
, then A+(m * 4)
means to add m * 4
to the pointer. (That is strange spacing, by the way. m
and 4
are more tightly bound by the higher-precedence multiplication than A
and (m * 4)
are by the addition, so why do they have looser spacing? A + m*4
would portray the meaning better.) Then A+(m * 4) + n
means to add n
to that. In total, we have moved m*4+n
elements beyond where A
points. Since A
points to a pointer, we have advanced the pointer by m*4+n
pointers. Then *((A+(m * 4) + n)))
dereferences that. When you dereference a pointer to a pointer to an int
, you get a pointer to an int
. So the result of this expression is a pointer. But you wanted an int
.
The link you reference talks about a “2D array”. The kinds of arrays it talks about are implemented using pointers to pointers. To create such an array, you create an array of pointers, and then you set each of those pointers to point to the elements of a row. Then a pointer to that array of pointers acts like a 2D array, in that A[i][j]
refers to element j
of row i
. If you had an array like that, you could refer to element n
of row m
using A[m][n]
. Equivalently, you could refer to it with *(*(A+m)+n)
. What this expression means is:
- Take the pointer
A
and add m
to it. Since A
points to a pointer to an int
, adding m
advances the value of the pointer to point to m
pointers further along. That is where we should find the pointer to the elements of row m
.
*(A+m)
gets the value of the pointer that A+m
points to. This value should be a pointer to the elements of row m
, specifically a pointer to the first element (with index 0).
*(A+m)+n
advances the value of the pointer to point n
int
further along. That is where we should find element n
of row m
.
*(*(A+m)+n)
gets the value of the int
that *(A+m)+n
points to.
Now suppose instead you changed print
to be print(int A[][4], int m, int n)
. Then your printf
statement should use A[m][n]
, just as before. Or it could use *(*(A+m)+n)
, also just as before. But, in this case, the expression is evaluated:
A
is a pointer to an array of 4 int
. Adding m
to it advances value of the pointer to point m
arrays further along.
*(A+m)
gets the object that A+m
points to. This object is an entire array. So this is an expression that designates an array. Following the C rule about arrays in expressions, this array is converted to a pointer to its first element. Thus, *(A+m)
becomes a pointer to the first element of the array numbered m
.
*(A+m)+n
advances the value of the pointer to point n
int
further along. That is where we should find element n
of row m
.
*(*(A+m)+n)
gets the value of the int
that *(A+m)+n
points to.
Thus A[m][n]
has the same end result for pointers-to-pointers, for pointers-to-arrays, and for arrays-of-arrays, but the steps it goes through for each are different. C knows the types of each subexpression and processes them differently, to achieve the same result.
Finally, suppose you pass &A[0][0]
to print
and change its parameter to int *A
. Now what is the expression *((A+(m * 4) + n)))
? In this case, you are treating the array of 3 arrays of 4 int
as one big array of 12 int
. Then you calculate where element n
of row m
is. In this case, A
is a pointer to int
(not a pointer to a pointer to int
). So A+(m * 4) + n
is a calculation to where element n
of row m
ought to be, and *((A+(m * 4) + n)))
gets the value of that element.
That is a method you ought to avoid when possible. Generally, you should use C’s built-in methods of addressing array elements and avoid doing your own calculations. Whether it is strictly conforming C code or not may depend on how pedantic you are about interpreting certain passages in the C standard.