I am trying to learn on 2D array in C and trying to implement the snipet code number 25 in page 59 of the book "Understanding pointers in C - Yashavant Kanetkar".
When compiling the code copied from the book I get the following warning:
upic_25.c:26:11: warning: incompatible pointer types passing 'int [3][4]' to parameter of type 'int *' [-Wincompatible-pointer-types]
display(a,3,4);
^
upic_25.c:8:19: note: passing argument to parameter 'q' here
void display(int *q, int row, int col){
^
1 warning generated.
Here is the snippet:
// understanding pointers in C - Yashavant Kanetkar
#include<stdio.h>
#include<stdlib.h>
void display(int *q, int row, int col);
void display(int *q, int row, int col){
int i, j;
for (i = 0; i<row; i ){
for(j = 0; j< col; j ){
printf("%d", *(q i *col j));
}
printf("\n");
}
}
int main(){
int a[][4] = {
5,7,5,9,
4,6,3,1,
2,9,0,6
};
display(a,3,4);
int *p;
int (*q)[4];
p = (int *) a;
q = a;
printf("%p %p", p, q);
p ;
q ;
printf("\n");
printf("%p %p", p, q);
printf("\n");
return 0;
}
What am I doing wrong?
CodePudding user response:
C is a strongly-typed language. This means that every object and every expression has a type, and the actual meaning of an expression very much depends on its type. For example, the expressions a = 7; b = 3; c = a / b
have fundamentally different behavior depending on which of a
, b
, and c
are integer or floating-point types.
Pointers, too, have types. A pointer is not "just an address". Any pointer has type "pointer to <othertype>
", where <othertype>
is obviously some other type.
A pointer must have a type, otherwise pointer arithmetic could not work. In the expression p i
, where p
is a pointer and i
is an integer, the actual address computed is the address value in p
, plus i * sizeof(*p)
. You have to know the type of the pointed-to object so you know what size to scale the offset by. Also, just as in a previous example, if p1
, p2
, and p3
are pointers, the expression *p1 = *p2 / *p3*
will have fundamentally different behavior depending on which of p1
, p2
, and p3
point to integer or floating-point types.
So with that background out of the way, now we can analyze the code in the question. Given the declaration
int a[][4]
the variable a
has type "array of some number of arrays of 4 int
".
But now let's create some pointers. The expression
&a
would generate a pointer to the entire array, and this pointer would have type "pointer to array of some number of arrays of 4 int
".
We can also create pointers to individual elements and subelements of a
. The expression
&a[0]
generates a pointer to a
's first element. Now, a
is an array of arrays, so its first element a[0]
is an array, namely its first row. So &a[0]
has type "pointer to array of 4 int
".
Consider the expression
&a[0][0]
Now, &a[0]
is a
's first row, so &a[0][0]
is the first element of the first row. It's an actual int
. So &a[0]]0]
has type "pointer to int
".
Finally, what if we just mention a
by itself in an expression, as in the function call
display(a, 3, 4);
Whenever you mention an array in an expression like that, the value you get is not "the whole array". Instead, due to a principle sometimes known as the correspondence between arrays and pointers, what you get is a pointer to the array's first element, or &a[0]
. We already figured that one out, just above. So when we call display(a, 3, 4)
, what gets passed as display
's first argument is a value of type "pointer to array of 4 int
".
But the definition of function display
does not say that. The definition of function display
says that its first argument is supposed to be of type int *
, or "pointer to int
". So this is why the compiler complained, and the warning you reported was precisely
warning: incompatible pointer types passing 'int [3][4]' to parameter of type 'int *'
Another compiler I tried, gcc, said
expected 'int *' but argument is of type 'int (*)[4]'
That curious notation int (*)[4]
is just another way of saying "pointer to array of 4 int
".
So how do we fix this? What function display
is trying to do is the somewhat dubious practice of "flattening" an array. Array a
is actually a
4×3 array, but since its three rows are going to be stored contiguously in memory, we can sort of think of it as a one-dimensional array of size 12. That's why display
accepts a
1-level pointer, int *
.
So a simple (though somewhat imperfect) fix, as I mentioned in a comment, is to change the call to
display(a[0][0], 3, 4);
Now we are passing a pointer to the array's first element's first element, which is a plain pointer-to-int
, which is what display
wants. display
wants a pointer to the first element of the "flattened" array it's going to print.
Now, you might have noticed that even when you called
display(a, 3, 4);
and got a warning, if you ignored the warning and tried running the resulting compiled program, it probably worked. That's because of a fact which may be surprising at first.
We said that the expressions &a
, &a[0]
, and &a[0][0]
all had different types. But it turns out they all have the same value. The address of the array is obviously the same as the address of the array's first element, and since the first element is an array, the address is also the same as the address of the array's first element's first element. That is, if we say
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
printf("&a[0][0] = %p\n", &a[0][0]);
printf("a = %p\n", a);
we will see exactly the same pointer value printed, four times. But, again, this does not mean that the pointers are "the same"! They have the same values, but different types.
Finally, if you're still with me, I have to explain what I meant when I said that flattening arrays is "somewhat dubious", and that my proposed fix was "imperfect". I had also said that the array's three rows are "going to be stored contiguously in memory", and they are, but, there's also a rule that says that a pointer can't be used outside of the object or array it points to. When we say &a[0][0], we get a pointer into the array's first row, but strictly speaking, we're not supposed to use that pointer outside of that first row. But function display
goes and uses it to step over the entire array.
As far as I know, "flattening" an array like this works, and the rulemongering which finds that it violates a constraint is more theoretical than practical, but this is what @tstanisl meant in a comment when they said in a comment that "using display(&a[0][0],3,4);
will result in UB when accessing q[4]
, and it's what I meant when I said that flattening arrays is "somewhat dubious". I'm not sure there's a better fix for the compiler warning you originally complained about, since the display
function's whole premise is slightly flawed.