0

I've seen this example in a text book and am a little confused how to interpret the operator precedence rules. Given this struct:

   typedef struct {
   char *data;
   size_t start, end;
   } str_wends;

Let B be declared as a pointer to this struct,that is.

   str_wends *B = malloc(sizeof(str_wends));

and assume B->start != 0,

then this should be a legal construct:

   &B->data[B->start]

which will return a reference to the string offset by B->start. The question is how to interpret the precedence rules. Since we know that -> and [] have the same precedence and bind left to right, why is B->Start evaluated before B->data[].

  • and assume B->start != 0, It is legal even if B->start == 0. – tkausl Aug 5 '15 at 23:28
  • why is B->Start evaluated before B->data[] -- Because you need the result of b->start before b->data[] can be evaluated. – Robert Harvey Aug 5 '15 at 23:30
  • @RobertHarvey you're right, but the compiler could evaluate b->data first, then b->start, and as last step, using the result of the already evaluated b->data and b->start to evaluate [*]. I am not sure but i think this is undefined behavior since it is almost the same problem here as with function-parameter evaluation. – tkausl Aug 5 '15 at 23:34
  • tkausl, you are correct. I was making the assumption because the case where B->start == 0 doesn't raise any concerns about the order. In that case the result is B->data (I think)? I guess I also assume that B->data != NULL as well. Just trying to avoid a degeneracy. – James S. Aug 6 '15 at 13:47
  • Writing it in the proper way to calculate pointer offset (B->data + B->start) is clearer and doesn't raise questions about precedence. Please cite the textbook, because the example, while valid, is pretty poor practice. – Blrfl Aug 7 '15 at 13:32
0

Concerning this:

&B->data[B->start]

you assert in comments that:

If I read the tokens from left to right, the operators are ->, [], ->

But those are not the operators reading left to right: you're including the closing ] with the opening [, even though the -> comes between them. That doesn't make any sense. When you see [expression], the inner expression doesn't have a precedence with respect to [] at all - it's an argument to it.

The code tokenizes as:

& B -> data [ B -> start ]

and the grammar rule for [] is approximately:

lvalue [ rvalue ]

This must reduce to lvalue:(B->data) [ rvalue:(B->start) ]. It cannot reduce to something like B -> (data [ B) -> (start ]), it simply isn't well-formed. This is an issue of grammar, not precedence.

  • This was exactly the issue I was having. Clearly ->, [], -> is wrong. With out the grammar rule for A[i], it seems to boil down to the evaluation of <PTR>[B->int] and nothing in precedence tells me what order to resolve the symbols. I guess it has to come from the grammar. Is this what happens when the substitution of (*((B->data)+(B->start))) is done? – James S. Aug 6 '15 at 18:40
  • I think the author of the question is asking how the difference between (&B->data)[B->start] and &(B->data[B->start]) is resolved internally by the compiler. – Jeremy Rodi Oct 25 '15 at 10:59
  • That's interesting: the author of the question commented more than two months ago, and immediately above your comment, that this answer covered "...exactly the issue I was having." They also accepted the answer. Are you suggested the author misunderstood his own question, or have I missed your point? – Useless Oct 25 '15 at 17:41
1

Actually, it doesn't matter whether B->start or B->data is "evaluated" first. The result of interest is address of the referenced element, which is the sum of the base address and the product of the index and the size of the element. The two addends are independent, meaning that the order of their evaluation is irrelevant.

The result of the addition is an lvalue. Evaluated on the right-hand side of an assignment expression, it generates a FETCH. Evaluated on the left-hand side, it generates a STORE.

  • What is not clear is the order of the membership operator -> nested inside an array subscript operator []. They have the same precedence. According to the GNU C reference A[i] is equivalent to (*((A)+(i))). In our case this would result in: (*((B->data)+(B->start))). The question then is whether this implicit addition that generates a clear precedence is the correct way to interpret the notation? – James S. Aug 6 '15 at 13:39
  • @JamesS.: No. Integer addition is commutative, even on computers. (Computer integer addition is not necessarily associative, if you take overflows into account.) All that matters is that both B->data and B->start have been "evaluated" before you attempt to add them. The compiler can evaluate either one first. Depending on the particular machine instruction set, it might make sense to evaluate one or the other first. Read "Design of an Optimizing Compiler", if you can find a copy (it is old, and HARD to find, and worth the effort!). – John R. Strohm Aug 6 '15 at 13:57
  • When it's written as (*((B->data)+(B->start))), it's clear that the B->data and B->start must be evaluated before + because () and -> are higher precedence than +. But when it's written as B->data[B->start] all the operators have the same precedence. If I read the tokens from left to right, the operators are ->, [], ->. I need to evaluate the last -> before I can evaluate the []. But nothing that I can see in the precedence rules tells me that. – James S. Aug 6 '15 at 14:14
  • Precedence doesn't tell you everything about the language, it's just for disambiguation and tie-breaking. It's well documented because it can be confusing, and because it's a fairly small table; the grammar by comparison is enormous, hard to read, and usually behaves the way you'd expect anyway. – Useless Aug 6 '15 at 16:27
  • Part of the problem is the insistence on C/C++ syntax. Other languages have simpler syntax and correspondingly smaller, simpler grammars. – John R. Strohm Aug 6 '15 at 16:40
1

Since we know that -> and [] have the same precedence and bind left to right, why is B->Start evaluated before B->data[].

This is a common misconception that just won't die.

Precedence and evaluation order are unrelated.

Precedence rules tell us what your statement means; which tokens form operands to which operators. It tells us that your code is equivalent to &(B->data[B->start]), not (&B->data)[B->start].

The order in which those operands are evaluated with respect to one another is guided by completely separate rules; in this case, it's obvious that B->Start must be evaluated before the surrounding B->data[].

University professors really need to stop saying that a higher precedence leads to something being "executed" "before" some other thing; it absolutely does not.

  • It does lead to that. Operands are evaluated in their evaluation order; operators are executed in their precedence and associativity order. You just need to understand the difference between operators and operands. – user207421 Aug 12 '16 at 12:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.