9

I am trying to learn pointers in C, and taking a quiz for this purpose. Here is the question:

#include <stdio.h>

char *c[] = {"GeksQuiz", "MCQ", "TEST", "QUIZ"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

int main()
{
    printf("%s ", **++cpp);
    printf("%s ", *--*++cpp+3);
    printf("%s ", *cpp[-2]+3);
    printf("%s ", cpp[-1][-1]+1);
    return 0;
}

The result of the line:

 printf("%s ", *cpp[-2]+3);

confuses me, but let me explain step by step, how I understand that.

  • char *c[] - is array of pointers to char.
  • char **cp[] - is array of pointers that points to pointer to char (I consider this as a wrapper for *c[] in reverse order).
  • char ***cpp - is a pointer to pointer that points to pointer to char (I consider this a wrapper for **cp[] to perform on place modifications).

**++cpp - since cpp points to cp, then ++cpp will point to cp+1 which is c+2, so double dereference will print TEST.

*--*++cpp+3 - since now cpp points to cp+1, then ++cpp will point to cp+2 which is c+1, and the next operation -- will give us pointer to c, so the last dereference will print sQuiz.

Here comes confusion:

cpp[-2] - since now cpp points to cp+2, which I can confirm with

printf("%p\n", cpp); // 0x601090   
printf("%p\n", cp+2); // 0x601090

here I print addresses of pointers in c

printf("c - %p\n", c); // c - 0x601060
printf("c+1 - %p\n", c+1); // c+1 - 0x601068
printf("c+2 - %p\n", c+2); // c+2 - 0x601070
printf("c+3 - %p\n", c+3); // c+3 - 0x601078

so when I dereference like this *(cpp[0]) or **cpp I expectedly get the value MCQ of c+1

printf("%p\n", &*(cpp[0])); // 0x601068

but when I say *(cpp[-2]) I get QUIZ, but I would rather expect to get some garbage value.

So my questions are:

  1. How the magic with *--*++cpp+3 works, I mean what is modified by the -- part that allows me to get MCQ instead of TEST when I dereference like this **cpp, I assume that this pointer *++cpp+3 preserves the state after the -- is applied, but cannot imagine yet how it works.

  2. Why the following works the way it works (the cpp[-2] part):

    printf("%p\n", &*cpp[1]); // 0x601060 -> c
    printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
    printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
    printf("%p", &*(cpp[-2])); // 0x601078 -> c+3
    

It seems that it has reverse order, I can accept &*(cpp[0]) pointing to c+1, but I would expect &*cpp[1] to point to c+2, and &*(cpp[-1]) to c. Which I found in this question: Are negative array indexes allowed in C?

  1. I obviously confuse many things, and may call something a pointer that in reality is not one, I would like to grasp the concept of pointer, so will be glad if someone show me where I am wrong.
Morten Jensen
  • 5,818
  • 3
  • 43
  • 55
MisterAlejandro
  • 111
  • 1
  • 5
  • 12
    If somebody thinks you can learn a language only by understanding such poor coding practice, they are mistaken. Abandon them. – R Sahu May 25 '18 at 18:35
  • `x[i]` is the same as `*(x+i)`. Play from here. I don't want to touch this code any further... – Eugene Sh. May 25 '18 at 18:38
  • Is `printf("%s ", *--*++cpp+3);` something you would ever use in clear code? – Weather Vane May 25 '18 at 18:39
  • 3
    What you might be missing is that the square brackets can be considered an operator, so that the term `cpp[n]` is really the same as `*(cpp + n)`. So a negative value for n is acceptable, but rarely used in practice. So, the negative subscript just moves backwards from the pointer cpp. – bruceg May 25 '18 at 18:40
  • 4
    @RSahu Seriously. I can't stand when teachers choose to give "gotcha" questions like this. It's terrible coding practice that nobody should ever have to deal with. If that ever makes it into production code, it means the company has failed to implement a bare-minimum code-review practice. – Christian Gibbons May 25 '18 at 18:45
  • In a previous life, and possibly now, engineering drawings were made to be understood by experienced practitioners. They didn't give any sops to newbies. But in the world of coding, where there is a rapid turnover of inexperienced workers, it is essential to write simple and well-commented code. If the job is complicated, that doesn't mean the code has to be impenetrable. – Weather Vane May 25 '18 at 18:54
  • @WeatherVane Some call it "employment insurance" :) Well, for me it would be rather a termination cause. – Eugene Sh. May 25 '18 at 18:57
  • 2
    I realize that this `*--*++cpp+3` is not how it should be written, but still those tricky question from the quiz have helped me to improve my understanding of pointers, don't be worry, you won't see my C code, I will just learn about pointers and leave) – MisterAlejandro May 25 '18 at 18:57
  • 2
    @MisterAlejandro it was a well asked question, please come back soon. – Weather Vane May 25 '18 at 19:02
  • 3
    @MisterAlejandro - We are just trying to say that leaning `*--*++cpp+3` is not important, because it is never used. I can't tell what is does, but I don't care (check my [grey beard](https://stackoverflow.com/users/597607/bo-persson?tab=profile) to see how long I have been working without needing this). You should focus your learning on things you can actually use. – Bo Persson May 25 '18 at 19:23
  • 1
    @MisterAlejandro: Indeed, it is a good exercise to understand how C is parsed, operators and precedence. – Acorn May 25 '18 at 19:37
  • I agree entirely with R Sahu and Bo Persson. Yes, it is possible to untangle the meaning of the absurd *--*++cpp+3 but it's simply not worth the effort. It is not something you will ever actually encounter, nor actually write. Your time would be much better spent with a different book. (I may not have Bo's grey beard, but I also have about 40 years coding experience. And I know a rat hole when I see one, and you are in one). – One Guy Hacking May 25 '18 at 19:43
  • I can only agree with the other commenters. If code is too complex to understand by just looking at it, it's too complex to debug and is not fit for purpose. – Martin James May 25 '18 at 21:00
  • 1
    `*--*++cpp+3` - *gaaaaaaaahhhhhhhh*. Somebody's getting a paddlin' over that. – John Bode May 25 '18 at 21:41

1 Answers1

2

Let me clarify first the negative index confusion, since we will use it later to answer the other questions:

when I say *(cpp[-2]) I get QUIZ, but I would rather expect to get some garbage value.

Negative values are fine. Note the following:

By definition, the subscript operator E1[E2] is exactly identical to *((E1)+(E2)).

Knowing that, since cpp == cp+2, then:

cpp[-2] == *(cpp-2) == *(cp+2-2) == *cp == c+3

And therefore:

*cpp[-2]+3 == *(c+3)+3 == c[3]+3

Which means the address of "QUIZ" plus 3 positions of a char pointer, so you are passing to printf the address of the character Z in "QUIZ", which means it will start printing the string from there.

Actually, in case you wonder, -2[cpp] is also equivalent and valid.


Now, the questions:

  1. How the magic with *--*++cpp+3 works, I mean what is modified by the -- part that allows me to get MCQ instead of TEST when I dereference like this **cpp, I assume that this pointer *++cpp+3 preserves the state after the -- is applied, but cannot imagine yet how it works.

Let's break it down (recall that cpp == cp+1 here, as you correctly point out):

    ++cpp   // cpp+1 == cp+2 (and saving this new value in cpp)
   *++cpp   // *(cp+2) == cp[2]
 --*++cpp   // cp[2]-1 == c (and saving this new value in cp[2])
*--*++cpp   // *c
*--*++cpp+3 // *c+3

And that points to sQuiz as you correctly pointed out. However, cpp and cp[2] were modified, so you now have:

cp[] == {c+3, c+2, c, c}
cpp  == cp+2

The fact that cp[2] changed is not used in the rest of the question, but it is important to note -- specially since you printed the values of the pointers. See:

  1. Why the following works the way it works (the cpp[-2] part):

    printf("%p\n", &*cpp[1]); // 0x601060 -> c
    printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
    printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
    printf("%p", &*(cpp[-2])); // 0x601078 -> c+3
    

First, let's simplify &*x into x. Then, doing something similar as above, if cpp == cp+2 (as above), you can see that:

cpp[ 1] == cp[3] == c
cpp[ 0] == cp[2] == c   // Note this is different to what you had
cpp[-1] == cp[1] == c+2
cpp[-2] == cp[0] == c+3
  1. I obviously confuse many things, and may call something a pointer that in reality is not one, I would like to grasp the concept of pointer, so will be glad if someone show me where I am wrong.

You actually got it quite well! :-)

Basically, a pointer is an integer that represents a memory address. However, when you perform arithmetic on it, it takes into account the size of the type it points to. That is the reason why, if c == 0x601060 and sizeof(char*) == 8, then:

c+1 == 0x601060 + 1*sizeof(char*) == 0x601068 // Instead of 0x601061
c+2 == 0x601060 + 2*sizeof(char*) == 0x601070 // Instead of 0x601062
Acorn
  • 24,970
  • 5
  • 40
  • 69
  • 1
    Yes, I did not pay enough attention to this `*((E1)+(E2))`, but now everything is so clear to me. And you are correct about `cpp[ 0] == cp[2] == c // Note this is different to what you had`, that happened because I commented out `printf("%s ", *--*++cpp+3);` and placed just `++cpp` on the line after, in order to test how it affects the result, and forgot to revert it back) – MisterAlejandro May 26 '18 at 04:23