++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);
See comments in-line:
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 a3
, 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.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:
Which generates the output:
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 toprintf()
.When things start out,
p->i
points to the string"Winter"
.++p->i: inter
— pre-increments the pointerp->i
so it points to thei
ofWinter
.p++->i: inter
— post-increments the pointerp
(to point to"Bash"
), but the result is the same as before because the increment takes effect afterp->i
is used.*p->i++: 66
— post-increments the pointerp->i
(so it points to thea
inBash
) and reports the value pointed at before the increment, which isB
(66 in ASCII).*p->i++: 97
— same expression, but the pointer points ata
(97) before the increment and at thes
after the increment.(*p->i)++: 115
— post-increments the letter thatp->i
points at, reportings
but changing it tot
.*p++->i: 116
— post-incrementsp
so it points to the string "In", while reportingt
(116).Here's an alternative with more instrumentation:
and its output:
Play with variants of this scheme (extra parentheses, for example) to ensure you understand what's going on.
First, some reminders:
p++
evaluates to the current value ofp
, and as a side effect adds 1 top
. Ifp
is a pointer, it is set to point to the next object in a sequence.++p
evaluates to the current value ofp + 1
, and as a side effect adds 1 top
. Again, ifp
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
is parsed as
and evaluates to the current value of
p->i
plus 1, and as a side effect updatesp->i
.The expression
is parsed as
and evaluates to the current
p->i
, then updatesp
to point to the nextstruct
object in a sequence.The expression
is parsed as
since
->
has higher precedence than unary*
. The operand of unary*
must have a pointer type, butp->i
is an integer, so the compiler will yak on this expression.The expression
is parsed as
Again, the compiler will yak on this expression, since the operand of
*
is not a pointer type.The expression
is parsed as
Again,
p->i
does not have pointer type, so the compiler will yak.The expression
is parsed as
and, again, more yakkage.
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 ifx
is a pointer to an struct. It is also equivalent to(*x).y
.++x
: Pre-incrementsx
. This increases the value ofx
by1
then returns the NEW value ofx
. It is lower priority than the->
operator, so++p->i
will fetchi
as above, then increment it.x++
: Post-incrementsx
. This increases the value ofx
by1
then returns the OLD value ofx
. But, this time, the operators are the same priority and get executed left to right. This will then incrementp
and then dereference wherep
used to be to accessi
. This will give you the value ofi
, but nowp
is pointing to uninitialized/unknown memory (unlessp
was in an array in which case it is now pointing to the next member of that array).*x
: Dereferencesx
just as I mentioned above under->
, but in this example we are now doing it twice, resulting in the equivalent((**p).i)++)
. However, sincep
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:
p
. (so-far-so-good)i
from that result.You can see the operator precedence rules I used here: https://en.cppreference.com/w/c/language/operator_precedence