p is a pointer to a structure, what do all these c

2019-06-01 14:38发布

问题:

++p->i 
p++->i
*p->i
*p->i++
(*p->i)++
*p++->i

I don't understand these statements above, I wrote a small test program to understand them.

#include <stdio.h>

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);
    printf("NUMBER: %d\n", p++->i);
    printf("RANK: %d", *p->i++);
    printf("name: %d\n", *p->i++);
    printf("number: %d\n", (*p->i)++);
    printf("rank: %d", *p++->i);
}

Here's what the output I got after I commented the last four print statements:

NAME: 21
NUMBER: 21

And after uncommenting the code and compiling I get:

test.c: In function 'main':
test.c:14:24: error: invalid type argument of unary '*' (have 'int')
printf("RANK: %d", *p->i++);
                    ^~~~~~~
test.c:15:26: error: invalid type argument of unary '*' (have 'int')
printf("name: %d\n", *p->i++);
                      ^~~~~~~
test.c:16:29: error: invalid type argument of unary '*' (have 'int')
printf("number: %d\n", (*p->i)++);

                         ^~~~~
test.c:17:24: error: invalid type argument of unary '*' (have 'int')
printf("rank: %d", *p++->i);

回答1:

It's possible to make the code work (compile, run without crashing, and produce a coherent, explicable answer), but you need a different structure from the one chosen. For example:

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %d\n", EXPR(*p++->i));
    return 0;
}

Which generates the output:

    ++p->i: inter
    p++->i: inter
   *p->i++: 66
   *p->i++: 97
 (*p->i)++: 115
   *p++->i: 116

The macro EXPR simply allows me not to repeat the expressions in the code, and yet to get both the string form and the value into the call to printf().

When things start out, p->i points to the string "Winter".

  • ++p->i: inter — pre-increments the pointer p->i so it points to the i of Winter.
  • p++->i: inter — post-increments the pointer p (to point to "Bash"), but the result is the same as before because the increment takes effect after p->i is used.
  • *p->i++: 66 — post-increments the pointer p->i (so it points to the a in Bash) and reports the value pointed at before the increment, which is B (66 in ASCII).
  • *p->i++: 97 — same expression, but the pointer points at a (97) before the increment and at the s after the increment.
  • (*p->i)++: 115 — post-increments the letter that p->i points at, reporting s but changing it to t.
  • *p++->i: 116 — post-increments p so it points to the string "In", while reporting t (116).

Here's an alternative with more instrumentation:

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p++->i));
    printf("%10s: %s\n", EXPR(p->i));

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    return 0;
}

and its output:

strings[0] = [Winter]
strings[1] = [Bash]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [Winter]
variables[1].i = [Bash]
variables[2].i = [Is]
variables[3].i = [Here]
      p->i: Winter
    ++p->i: inter
      p->i: inter
    p++->i: inter
      p->i: Bash
   *p->i++: 66
      p->i: ash
   *p->i++: 97
      p->i: sh
 (*p->i)++: 115
      p->i: th
   *p++->i: 116
      p->i: Is
strings[0] = [Winter]
strings[1] = [Bath]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [inter]
variables[1].i = [th]
variables[2].i = [Is]
variables[3].i = [Here]

Play with variants of this scheme (extra parentheses, for example) to ensure you understand what's going on.



回答2:

First, some reminders:

p++ evaluates to the current value of p, and as a side effect adds 1 to p. If p is a pointer, it is set to point to the next object in a sequence.

++p evaluates to the current value of p + 1, and as a side effect adds 1 to p. Again, if p is a pointer, it is set to point to the next object in a sequence.

The postfix form of ++ and the -> operator have the same precedence, and they have higher precedence than the unary (prefix) form of ++ and *. Thus, an expression like ++p->i is parsed as ++(p->i), p->i++ is parsed as (p->i)++, *p->i is parsed as *(p->i), etc.

With that out of the way...

The expression

++p->i

is parsed as

++(p->i)

and evaluates to the current value of p->i plus 1, and as a side effect updates p->i.

The expression

p++->i

is parsed as

(p++)->i

and evaluates to the current p->i, then updates p to point to the next struct object in a sequence.

The expression

*p->i

is parsed as

*(p->i)

since -> has higher precedence than unary *. The operand of unary * must have a pointer type, but p->i is an integer, so the compiler will yak on this expression.

The expression

*p->i++

is parsed as

*((p->i)++)

Again, the compiler will yak on this expression, since the operand of * is not a pointer type.

The expression

(*p->i)++

is parsed as

(*(p->i))++

Again, p->i does not have pointer type, so the compiler will yak.

The expression

*p++->i

is parsed as

*((p++)->i)

and, again, more yakkage.



回答3:

Let's break this down by operator.

x->y: This dereferences a pointer to an struct (x) and then accesses the indicated member variable (y). It is only meaningful if x is a pointer to an struct. It is also equivalent to (*x).y.

++x: Pre-increments x. This increases the value of x by 1 then returns the NEW value of x. It is lower priority than the -> operator, so ++p->i will fetch i as above, then increment it.

x++: Post-increments x. This increases the value of x by 1 then returns the OLD value of x. But, this time, the operators are the same priority and get executed left to right. This will then increment p and then dereference where p used to be to access i. This will give you the value of i, but now p is pointing to uninitialized/unknown memory (unless p was in an array in which case it is now pointing to the next member of that array).

*x: Dereferences x just as I mentioned above under ->, but in this example we are now doing it twice, resulting in the equivalent ((**p).i)++). However, since p is pointing to a struct and not pointing to a pointer to a struct, this is a compiler error. This goes for the next expression too as it just explicitly spells out the same order of operations that the compiler will already conform to.

Putting that all together we come to the last one, which will in order:

  1. Dereference p. (so-far-so-good)
  2. Increment that result. But increment isn't defined for a struct, so compiler error.
  3. Dereference that result. Again, no longer a pointer, so we can't dereference. Compiler error.
  4. Access member i from that result.

You can see the operator precedence rules I used here: https://en.cppreference.com/w/c/language/operator_precedence



回答4:

See comments in-line:

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);     //pre-increments i by 1 (prints 21)
    printf("NUMBER: %d\n", (p++)->i);   //changes location pointed to. (meaningless, ub, printed 3 for me)
    //printf("RANK: %d", *p->i++);      // error, (will not be included in build)
    //printf("name: %d\n", *p->i++);      // error, (will not be included in build)
    //printf("number: %d\n", (*p->i)++);// error, (will not be included in build)
    //printf("rank: %d", *p++->i);    // error, (will not be included in build)

    getchar();
}

Although both the first and second statements are syntactically correct (i.e. compile, build and run without any indication of problem) only the first makes sense, although for what purpose, I do not know. Given the declaration: struct my_structure variable = {20};, only defines a single memory location, the second increment of the pointer places its location beyond your definition, and pointing to an unknown value. In my run, it pointed to a 3, but could have contained anything. And, since it was not owned, invokes undefined behavior. This is why it would be very likely that running the resultant executable, on different PCs, or even on the same PC at different times would produce varying results.