-2

Can somebody explain what is happening with the precedence in this code? I've be trying to figure out what is happening by myself but I could'nt handle it alone.

#include <stdio.h>

int main(void) {
   int v[]={20,35,76,80};
   int *a;
   a=&v[1];
   --(*++a);

  printf("%d,%d,%d,%d\n",v[0],v[1],v[2],v[3]);

  (*++a);

  printf("%d\n", *a);

  *a--=*a+1; // WHAT IS HAPPENING HERE?

  printf("%d\n", *a);

  printf("%d,%d,%d,%d\n",v[0],v[1],v[2],v[3]);

}


//OUTPUT
20,35,75,80
80
75
20,35,75,76
  • 4
    Which one do you mean? And what is your specific problem about that expression you don't state? – too honest for this site May 19 '17 at 20:07
  • 2
    You are playing with fire here... – Eugene Sh. May 19 '17 at 20:08
  • I've just added what is the expression and the output... – trunobo nobo May 19 '17 at 20:10
  • 3
    If you stop writing badly structured code that is difficult to understand, then you will be able to understand what is going on. What possessed you to write stuff like '--(*++a);' and '*a--=*a+1;' ?? Split up those statements so that what you want is obvious and easy to debug! Don't do it again! – ThingyWotsit May 19 '17 at 20:14
  • 3
    Undefined behavior since you are reading from and modifying `a` without an intervening [sequence point](https://en.wikipedia.org/wiki/Sequence_point). – jamesdlin May 19 '17 at 20:14
  • 1
    The "WHAT IS HAPPENING HERE" line is *undefined*. See http://stackoverflow.com/questions/949433/why-are-these-constructs-using-undefined-behavior/ . – Steve Summit May 19 '17 at 20:14
  • 1
    Note that `(*++a);` is the same as `++a;`. – Jonathan Leffler May 19 '17 at 20:16
  • 1
    Possible duplicate of [multiple '++' working in variables, and pointers](http://stackoverflow.com/questions/1720079/multiple-working-in-variables-and-pointers) – AShelly May 19 '17 at 20:17
  • Notably, the `=` is not a sequence point. You can't make any assumptions about when the `--` happens in relation to the rest of the operations. – AShelly May 19 '17 at 20:19
  • Also, modern gcc will probably produce [nasal demons](http://www.catb.org/jargon/html/N/nasal-demons.html). – AShelly May 19 '17 at 20:21
  • http://en.cppreference.com/w/c/language/operator_precedence print this chart out and hang it next to your computer – lost_in_the_source May 19 '17 at 20:30
  • @stackptr Operator precedence has nothing whatsoever to do with understanding the problematic aspect of the "WHAT IS HAPPENING HERE?" line. – Steve Summit May 20 '17 at 10:26

3 Answers3

2

*a--=*a+1; // WHAT IS HAPPENING HERE?

What's happening is that the behavior is undefined.

6.5 Expressions
...
2    If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.84)

3    The grouping of operators and operands is indicated by the syntax.85) Except as specified later, side effects and value computations of subexpressions are unsequenced.86)

C 2011 Online Draft (N1570)

The expressions *a-- and *a are unsequenced relative to each other. Except in a few cases, C does not guarantee that expressions are evaluated left to right; therefore, it's not guaranteed that *a-- is evaluated (and the side effect applied) before *a.

*a-- has a side effect - it updates a to point to the previous element in the sequence. *a + 1 is a value computation - it adds 1 to the value of what a currently points to.

Depending on the order that *a-- and *a are evaluated and when the side effect of the -- operator is actually applied, you could be assigning the result of v[1] + 1 to v[0], or v[1] + 1 to v[1], or v[0] + 1 to v[0], or v[0] + 1 to v[1], or something else entirely.

Since the behavior is undefined, the compiler is not required to do anything in particular - it may issue a diagnostic and halt translation, it may issue a diagnostic and finish translation, or it may finish translation without a diagnostic. At runtime, the code may crash, you may get an unexpected result, or the code may work as intended.

Community
  • 1
  • 1
John Bode
  • 119,563
  • 19
  • 122
  • 198
1

I'm not going to explain the whole program; I'm going to focus on the "WHAT IS HAPPENING HERE" line. I think we can agree that before this line, the v[] array looks like this, with a pointing at v's last element:

   +----+----+----+----+
v: | 20 | 35 | 75 | 80 |
   +----+----+----+----+
      0    1    2    3
                     ^
                   +-|-+
                a: | * |
                   +---+

Now, we have

*a-- = *a+1;

It looks like this is going to assign something to where a points, and decrement a. So it looks like it will assign something to v[3], but leave a pointing at v[2].

And the value that gets assigned will evidently be the value that a points to, plus 1.

But the key question is, when we take *a+1 on the right-hand side, will it use the old or the new value of a, before or after the decrement on the right-hand side? It turns out this is a really, really hard question to answer.

If we take the value after the decrement, it'll be a[2], plus 1, or 76 that gets assigned to a[3]. It looks like that's how your compiler interpreted it. And this makes a certain amount of sense, because when we read from left to right, it's easy to imagine that by the time we get around to computing *a+1, the a-- has already happened.

Or, if we took the value before the decrement, it would be a[3], plus 1, or 81 that gets assigned to a[3]. And that's how it was interpreted by three different compilers I tried it on. And this makes a certain amount of sense, too, because of course assignments actually proceed from right to left, so it's easy to imagine that *a+1 happens before the a-- on the left-hand side.

So which compiler is correct, yours or mine, and which is wrong? This is where the answer gets a little strange, and/or surprising. The answer is that neither compiler is wrong. This is because it turns out that it's not just really hard to decide what should happen here, it is (by definition) impossible to figure out what happens here. The C standard does not define how this expression should behave. In fact, it goes one farther than not defining how this expression should behave: the C Standard explicitly says that this expression is undefined. So your compiler is right to put 76 in v[3], and my compilers are right to put 81. And since "undefined behavior" means that anything can happen, it wouldn't be wrong for a compiler to arrange to put some other number into v[3], or to end up assigning to something other than v[3].

So the other part of the answer is that you must not write code like this. You must not depend on undefined behavior. It will do different things under different compilers. It may do something completely unpredictable. It is impossible to understand, maintain, or explain.

It's pretty easy to detect when an expression is undefined due to order-of-evaluation ambiguity. There are two cases: (1) the same variable gets modified twice, as in x++ + x++. (2) The same variable gets modified in one place, and used in another, as in *a-- = *a+1.

It's worth noting that one of the three compilers I used said "eo.c:15: warning: unsequenced modification and access to 'a'", and another said "eo.c:15:5: warning: operation on ‘a’ may be undefined". If your compiler has an option to enable warnings like these, use it! (Under gcc it's -Wsequence-point or -Wall. Under clang, it's -Wunsequenced or -Wall.)

See John Bode's answer for the detailed language from the C Standard that makes this expression undefined. See also the canonical StackOverflow question on this topic, Why are these constructs (using ++) undefined behavior?

Community
  • 1
  • 1
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
-1

Not exactly sure which expression you have problems with. Increment and decrement operators have the highest precedence. Dereference comes after. Addition, substraction, after.

But with regards to assignment, C does not specify order of evaluation (right to left or left to right). will right hand side of an expression always evaluated first

C does not specify which of the right hand side or left hand side of the = operator is evaluated first.

*a--=*a+1;

So it could be that your pointer a is decremented first or after it's dereferenced on the right hand side.

In other words, depending on the compiler this expression could be equivalent to either:

a--;
*a = *a+1;

or

*(a-1)=*a+1;
a--;

I personally never rely too much on operator precedence in my code. I makes it more legible to either put parenthesis or separate in different lines.

Unless you're building a compiler yourself and need to make a decision to what assembly code to generate.

Community
  • 1
  • 1
Ronald
  • 719
  • 1
  • 6
  • 6
  • Two things: You said, "C does not specify which side of the = operator is evaluated first", but when it comes to expressions like this, C does not specify "which side" of *any* operator gets evaluated first. Also, adding parentheses doesn't usually help. – Steve Summit May 20 '17 at 10:23