0

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?

ecjb
  • 5,169
  • 12
  • 43
  • 79
  • 2
    Your problem is that Yashavant Kanetkar's books are garbage. There's a typographical error in the title. It should be *Misleading Information About Pointers in C*. Yashavant Kanetkar does not teach you the actual C language. He teaches a strange non-language which he has deduced after long experimentation with an ancient copy of Turbo C. – Steve Summit Oct 11 '22 at 11:55
  • No offense, but that book is a singularly bad source of learning C. It took me years to un-learn all the wrong concepts. – Sourav Ghosh Oct 11 '22 at 11:56
  • The simplest fix is to change the call `display(a,3,4);` to `display(&a[0][0],3,4);`. Also, for your sanity, I would change the `"%d"` in the first `printf` call to `"%d "`. Spaces are cheap; there's no need to jam the output digits together as they were originally. – Steve Summit Oct 11 '22 at 12:01
  • Seriously, your best bet may very well be to throw that book away. Yashavant Kanetkar's books are popular, and I suppose they do a good job of teaching the basics of C to beginners, at the unfortunate cost of a certain amount of misinformation mixed in. But I can't imagine how many misleading or outright wrong explanations there must be in a book of his dedicated solely to an important but difficult concept like pointers. (In particular: find the sentence where it says "An array name is a constant pointer" — I'm sure it's there somewhere — and blacken it out with a Sharpie® marker.) – Steve Summit Oct 11 '22 at 12:05
  • 1
    But it's good — very good — that your compiler gave you a warning, and that you paid attention to it, and that you asked about it here. You did nothing wrong: the fault is all the author's. – Steve Summit Oct 11 '22 at 12:07
  • Also the author of *Let Us C* which contains this howler shown in a previous SO question [Why does this code print 1 2 2 and not the expected 3 3 1?](https://stackoverflow.com/questions/63505019/why-does-this-code-print-1-2-2-and-not-the-expected-3-3-1) – Weather Vane Oct 11 '22 at 12:16
  • @WeatherVane Also some commentary at [this now-deleted question](https://stackoverflow.com/questions/58114529). As I commented then: "It's bad enough to make ill-informed guesses, but then to present them as established fact takes a level of deranged chutzpah that always just floors me when I encounter it." – Steve Summit Oct 11 '22 at 12:19
  • @SteveSummit the source of half the SO questions? – Weather Vane Oct 11 '22 at 12:21
  • @WeatherVane Well, maybe not, but certainly the source of `i++ + i++` of them. :-) – Steve Summit Oct 11 '22 at 12:23
  • @SteveSummit. Many thanks for your comments. I have to say that maybe this book is of bad quality but what I found appealing with it was that it adress one of the most difficult difficult subjects of C for beginners (namely pointers) with many different exercices giving the opportunity to be exposed to that subject from many different angles. I found that other books skim the subject theoretically without giving opportunity to exercice. Could you recommend an alternative good book specifically on pointers with similar amount and variety of exercices on pointers? – ecjb Oct 11 '22 at 12:45
  • @SteveSummit, using `display(&a[0][0],3,4);` will result in UB when accessing `q[4]`. The only thing that may work is `display((int*)&a,3,4);` though the standard is bit vague about it. The proper solution should use VLA types. – tstanisl Oct 11 '22 at 12:57
  • 1
    @ecjb Unfortunately I don't have anything specifically on pointers to recommend. *C: A Modern Approach* by K.N. King was once much recommended. I can recommend my own on-line C Programming Notes (beginning and intermediate) at https://www.eskimo.com/~scs/cclass/cclass.html . – Steve Summit Oct 11 '22 at 12:57
  • Thanks a lot @SteveSummit. I will look carefully at your notes and look to the book of K.N. King – ecjb Oct 11 '22 at 13:06

1 Answers1

1

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103