*p++->str : Understanding evaluation of ->
Asked Answered
V

2

8

My question is about the following line of code, taken from "The C Programming Language" 2nd Edition:

*p++->str;

The book says that this line of code increments p after accessing whatever str points to.

My understanding is as follows:

  • Precedence and associativity say that the order in which the operators will be evaluated is

    1. ->
    2. ++
    3. *
  • The postfix increment operator ++ yields a value (i.e. value of its operand), and has the side effect of incrementing this operand before the next sequence point (i.e. the following ;)

  • Precedence and associativity describe the order in which operators are evaluated and not the order in which the operands of the operators are evaluated.

My Question:

My question is around the evaluation of the highest precedence operator (->) in this expression. I believe that to evaluate this operator means to evaluate both of the operands, and then apply the operator.

From the perspective of the -> operator, is the left operand p or p++? I understand that both return the same value.

However, if the first option is correct, I would ask "how is it possible for the evaluation of the -> operator to ignore the presence of the ++".

If the second option is correct, I would ask "doesn't the evaluation of -> in this case then require the evaluation of a lower precedence operator ++ here (and the evaluation of ++ completes before that of ->)"?

Vernal answered 30/6, 2019 at 15:39 Comment(5)
The operand to -> is p++, and the reason their relative precedence doesn’t matter is that ++ is a unary operator. (But order of evaluation doesn’t follow precedence anyway.)Sympathize
The ++ increment occurs before the next 'sequence point'. There isn't a sequence point within the expression *p++->string, so the increment occurs before the next sequence point in the larger context in which the expression appears.Leucas
Put simply, ++ isn't an expression, it's an operator acting on p, so the operand has to be p++ and not p.Uphroe
*p++->str; is clearly bad code. It's hard enough to understand that it generated a seven-plus paragraph question, didn't it?Rodenticide
@AndrewHenle Almost as fun as (a=1)+(b=a);.Noctambulism
B
6

To understand the expression *p++->str you need to understand how *p++ works, or in general how postfix increment works on pointers.

In case of *p++, the value at the location p points to is dereferenced before the increment of the pointer p.
n1570 - §6.5.2.4/2:

The result of the postfix ++ operator is the value of the operand. As a side effect, the value of the operand object is incremented (that is, the value 1 of the appropriate type is added to it). [...]. The value computation of the result is sequenced before the side effect of updating the stored value of the operand.

In case of *p++->str, ++ and -> have equal precedence and higher than * operator. This expression will be parenthesised as *((p++)->str) as per the operator precedence and associativity rule.

One important note here is precedence and associativity has nothing to do with the order of evaluation. So, though ++ has higher precedence it is not guaranteed that p++ will be evaluated first. Which means the expression p++ (in the expression *p++->str) will be evaluated as per the rule quoted above from the standard. (p++)->str will access the str member p points to and then it's value is dereferenced and then the value of p is incremented any time between the last and next sequence point.

Bakker answered 30/6, 2019 at 15:58 Comment(7)
Interesting. My book states the highest tier of precedence as () [] -> . (left to right), and the next tier as ! - ++ -- + - * & (type) sizeof (right to left). Therefore, I thought that -> operator had highest precedence here. However, I can see that this page agrees with your analysisVernal
@Vernal The ++ and -- in the second tier are prefix operators. Your book is missing the postfix ++ and -- in the first tier.Sweetbread
@Sweetbread Thank you. I've done some research and found this post which seems to relate to the same point. Thanks for clearing this up for me !Vernal
@Vernal minor side point, that reference you first linked isn't quite correct, as the cast operator has a lower precedence than shown.Uphroe
Re: “So, though ++ has higher precedence it is not guaranteed that p++ will be evaluated first”: It is guaranteed the value computation of ++ occurs before ->. Per C 2018 6.5 1, “The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.” The side effect is not sequenced by this, but the value computation is.Raddle
@EricPostpischil; In the expression *p++->str is there any guaranty that p++ will evaluated before str? All I know is both p++ and str will be evaluated before the value computation of the result of -> operator. But it is not guaranteed that p++ will be evaluated before or after str.Bakker
@haccks: I do not see anything in the C standard that says whether the first operand (p++) or the second operand (str) of -> is evaluated first. But it would not be meaningful because str is not an expression. It is merely a name for a member. We can have lvalue expressions like p or *p that are evaluated, but the name of a member of a structure is not an lvalue; it is just a name, the same way int is just a name in sizeof(int).Raddle
S
6

Postfix ++ and -> have the same precedence. a++->b parses as (a++)->b, i.e. ++ is done first.

*p++->str; executes as follows:

  • The expression parses as *((p++)->str). -> is a meta-postfix operator, i.e. ->foo is a postfix operator for all identifiers foo. Postfix operators have the highest precedence, followed by prefix operators (such as *). Associativity doesn't really apply: There is only one operand and only one way to "associate" it with a given operator.

  • p++ is evaluated. This yields the (old) value of p and schedules an update, setting p to p+1, which will happen at some point before the next sequence point. Call the result of this expression tmp0.

  • tmp0->str is evaluated. This is equivalent to (*tmp0).str: It dereferences tmp0, which must be a pointer to a struct or union, and gets the str member. Call the result of this expression tmp1.

  • *tmp1 is evaluated. This dereferences tmp1, which must be a pointer (to a complete type). Call the result of this expression tmp2.

  • tmp2 is ignored (the expression is in void context). We reach ; and p must have been incremented before this point.

Scales answered 30/6, 2019 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.