0

I'm currently learning C, and my teacher gave us some homework. We had to identify the output of some code. I don't understand how y=4.

The code is as follows

int main() {

    int w = 3, y = 3;
    printf("w = %i\n", --w + w--);
    printf("y = %i\n\n", y = w + y++);

    return 0;
}
hivert
  • 10,579
  • 3
  • 31
  • 56
Nxt3
  • 1,970
  • 4
  • 30
  • 52
  • Do you know what the values of `w` and `y` are immediately before you evaluate `printf("y = %i\n\n", y = w + y++);`? There is undefined behavior here in `--w + w--`. If you replaced the first `printf(...)` with `w = 1`, would you understand the output? – Joshua Taylor Feb 19 '14 at 17:23
  • @mbratch I agree that there's undefined behavior here; but Nate _does_ have this homework, and the question might actually not depend on it. It depends on exactly where the confusion is. If we can remove the first `printf` and let `w` be 1, then if the question still stands, it's answerable. – Joshua Taylor Feb 19 '14 at 17:26
  • 3
    If this was homework, tell your teacher to read a basic C tutorial. – devnull Feb 19 '14 at 17:28
  • @JoshuaTaylor yes, that is a good point – lurker Feb 19 '14 at 17:29
  • I don't understand how w becomes 1 for the second statement. The current code prints`w=4` and `y=4`. I understand how the first print statement works, but don't see how that leaves the second print statement with `w=1` – Nxt3 Feb 19 '14 at 17:32
  • 6
    Your teacher is trolling you. – mr5 Feb 19 '14 at 17:36
  • @Nate - w is never `4`. That is just the value of `--w + w++`. You could have done `printf("my bank account has %d millions in it", w);`. That doesn't make it so. – Floris Feb 19 '14 at 17:37

4 Answers4

5

The behaviour is undefined since you modify the value of a variable twice between two sequence points.

You should read more about sequence points http://en.wikipedia.org/wiki/Sequence_point .This is what the Standard says about it:

At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

Emil Condrea
  • 9,705
  • 7
  • 33
  • 52
  • Since C 2011, this undefined behavior is no longer based on sequence points but rather upon two side effects or a side effect and a value computation are unsequenced. A sequence point is sufficient to cause sequencing but is not necessary. – Eric Postpischil Feb 19 '14 at 17:35
  • @EricPostpischil you're right, I think I read somewhere a sentence like : "Why would they remove sequence points concept if we just started to understand it" – Emil Condrea Feb 19 '14 at 21:08
  • 1
    By the way, I may be wrong about a sequence point causing sequencing. In `f() + g()`, there are sequence points involved in calling and returning from functions, so there **must** be a sequence point between the expressions inside `f` and those inside `g`, but the order of them is still not determined because either `f` or `g` could be called first. – Eric Postpischil Feb 19 '14 at 21:27
4

Let me preface this with an important public service announcement:

When your compiler gives you a warning, any warning, you would do well to pay attention to it. The code example in your question gives rise to Undefined Behavior - in other words, the C standard does not guarantee that every compiler will give the same results, given your code. That is Always a Bad Thing.

With that out of the way, here is an explanation of what is going on.

I modified your code slightly to look at the intermediate value of w:

#include <stdio.h>

int main(void) {

    int w = 3, y = 3;
    printf("w = %i\n", --w + w--);
    printf("w is actually %d\n", w);
    printf("y = %i\n\n", y = w + y++);

    return 0;
}

My compiler complains about this code with the following warnings:

order.c:6:24: warning: multiple unsequenced modifications to 'w' [-Wunsequenced]
    printf("w = %i\n", --w + w--);
                       ^      ~~
order.c:8:35: warning: multiple unsequenced modifications to 'y' [-Wunsequenced]
    printf("y = %i\n\n", y = w + y++);
                           ~      ^
2 warnings generated.

And the output is

w = 4
w is actually 1
y = 4

What is happening? Since compilers parse the statement left-to-right, the statement

--w + w--

seems to result in the following steps (for my compiler):

--w  : decrement w, it is now 2
w--  : use the value of '2', but decrement w when you are done

The result is that the sum is 2+2 and the first line prints 4, but after the sum is evaluated w is decremented to 1. However you cannot rely on this behavior - which is why the compiler threw a warning. As Eric Postpischil pointed out there may be situations where the order of execution might be different. "Don't do it" is the bottom line - you are courting Undefined Behavior.

Note that the fact that your program printed w equals 4 doesn't mean that this was true at any time. As you can see, when you actually print out w it is 1.

Assuming the compiler executed these statements in the above order, we go to the next line:

y = w + y++

We start with w = 1 and y = 3. The sum of these two is 4, and that is the value of the expression

y = w + y++

which is therefore what is printed (4).

As a side effect, y is incremented. As it happens, either way the value of y is 4 at the end. If you change the code so you start with w = 5, you end up with y = 6. In other words, the effect of y++ got overridden by the y = assignment.

Floris
  • 45,857
  • 6
  • 70
  • 122
  • This makes a ton of sense. I really appreciate it! – Nxt3 Feb 19 '14 at 17:37
  • 2
    Compilers do not “operate left-to-right”. Much of the parsing may occur left to right, but there are multiple layers in language translation. Consider code such as `x = E2; printf("%d", E1 + E2); …`, where `E1` and `E2` stand for compound expressions. Since `E2` is computed once for `x`, the compiler may decide to use that result again for the `E2` in `E1 + E2`, resulting in `E2` being computed before `E1`, even though it is on the right. – Eric Postpischil Feb 19 '14 at 17:40
  • @Nate - you are welcome. As an afterword - **always** heed the warnings of your compiler. If your code doesn't compile without any warnings, chances are it can be improved. When you are learning that is even more important. – Floris Feb 19 '14 at 17:40
  • @EricPostpischil - as long as the evaluation of `E2` has no side effects, what you say is true. But if `E2` is `y++` I am sure it will be evaluated twice in your example - and I am reasonably sure that the second evaluation will take place after `E1`. But I agree that it is courting problems to rely on that behavior - see my comment about heeding the warnings of your compiler. The warning is there because you shouldn't write code like that. How would you propose I re-phrase my statement? – Floris Feb 19 '14 at 17:42
  • 1
    @Floris: You are wrong about being sure the second evaluation will take place after `E1`. Even if `E2` contains side effects, the fact that all of it has been computed previously means that parts of it have been computed previously, so it is more efficient to **use those parts** while they are still in registers and compute the new value (with updated side effects et cetera) before reusing the registers to compute `E1`. **The statement that compilers operate left-to-right is false and dangerous.** – Eric Postpischil Feb 19 '14 at 17:47
  • I modified my answer, stating only that they parse left to right, and that my compiler evaluated left to right; but I emphasized that this is undefined behavior. Is that more accurate? – Floris Feb 19 '14 at 17:49
  • Marginally. The most important message to convey in these cases is that the behavior is undefined. That ought to be first and foremost. Explaining how particular behavior observed in one instance came to be can be useful so that people understand how compilers and other technology work, but it needs to be presented in a way that shows what happened without overshadowing the critical knowledge that the behavior is undefined. As in “The airbag deployed and saved your life, but you should **never** drive that way.” – Eric Postpischil Feb 19 '14 at 18:00
  • @EricPostpischil - thanks for your insistence on getting this right. I have edited the answer again, with a Big Banner right at the top. I agree that this is an important message. – Floris Feb 19 '14 at 19:12
3

Undefined Behavior

There is undefined behavior (as described in Why are these constructs (using ++) undefined behavior?) in this code in (at least) these two parts:

--w + w--

y = w + y++

This means that the results you're getting aren't portable or guaranteed.

Lying Code

On top of the undefined behavior, the output actually lies to you. printf("w = %i\n", --w + w--); looks like it's printing the value of w (in that it prints w = …), but it's not printing the value of w, it's printing the value of --w + w-- (which is undefined, so it's actually printing the value of whatever the compiler compiled --w + w-- to).

So what the value of w?

The code your compiler has generated for --w + w-- has the side effect of making w's value be 1, since it is originally 3, and both --w and w-- decrement w. However, the value of --w + w-- (in the code that your compiler generated is, based on the output that you're getting, 4. What could cause this? Well, --w could be evaluated first, which decrements w from 3 to 2, and returns the new value of w, which is 2. Then w-- could be evaluated, which decrements w from 2 to 1, but returns the original value of w, which is 2. The sum of 2 and 2, of course, is 4.

So, the value of --w + w-- is 4, and the new value of w is 1.

And what happens with y?

w's new value is 1, so when you execute (recalling that the value of y is 3):

printf("y = %i\n\n", y = w + y++);

The side effect of y++ is that y is incremented (but it's undefined whether this happens before or after the assignment), but the value of the expression is the previous value of y, which in this case is 3. Therefore, you're adding w, whose value is 1, and and the value of y++, which is 3. You're then assigning 4 to y, and the value of that assignment is 4, so you get 4 as the output. There's still behavior that might be undefined in y = w + y++ because it's not clear when the increment of y should be happening: before or after the assignment?

Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • If you need to refer to an existing answer, chances are that the question is a dup. – devnull Feb 19 '14 at 17:30
  • `codepad` gave me `4` for `w` and `4` for `y`. ideone gave me `3` for `w` and `4` for `y`. – Engineer2021 Feb 19 '14 at 17:30
  • @devnull That's often the case; however, in this case, OP is running some code that has undefined behavior, and is asking for an explanation of the behavior that's _actually_ happening. I think it's worth pointing out that it's not guaranteed behavior (which is what the cited answer does), but also explaining the behavior of what the compiler apparently produced. – Joshua Taylor Feb 19 '14 at 17:32
  • @GIJoe But JoshuaTaylor feels that output can be well explained in this case. – devnull Feb 19 '14 at 17:34
  • Your answers seems to suggest that saying `y = w + y++` is well-defined. I'm not certain if that is correct. Time to earn the Disciplinary badge? – devnull Feb 19 '14 at 17:39
  • @GIJoe are you sure codepad gave you `4` for `w`, and not just as the output of the `printf` statement (which doesn't actually print the value of `w`, it just claims that it does). – Floris Feb 19 '14 at 17:39
  • @devnull I feel that since the OP has told us what each `printf` outputs, we can make a reasonable guess about what the compiler produced, and why the output is what it is. That it's undefined behavior means that the compiler could have produced something else, too. – Joshua Taylor Feb 19 '14 at 17:39
  • @Floris: I ran it twice and got `4`. – Engineer2021 Feb 19 '14 at 17:40
  • 1
    @GIJoe Floris' point was that even though it's `print( "w = … " );`, it's not actually printing the value of `w`. It's lying to us. :) – Joshua Taylor Feb 19 '14 at 17:40
  • @JoshuaTaylor: Oh, gotcha. That makes sense – Engineer2021 Feb 19 '14 at 17:41
  • @devnull I've already got that badge, but maybe it's time to earn it again… :) – Joshua Taylor Feb 19 '14 at 17:44
1

Your teacher is assuming a specific order of evaluation which is not guaranteed by the language.

Assuming strict left-to-right evaluation, and assuming that side effects are applied immediately, then given the starting values for w and y, the following things happen:

  1. --w evaluates to w-1 (2), and as a side effect decrements w; w is now 2
  2. w-- evaluates to w (2), and as a side effect decrements w; w is now 1
  3. As a result of 1 and 2, --w + w-- evaluates to 4
  4. y++ evaluates to y, and as a side effect increments y; y is now 4
  5. As a result of 4, w + y++ evaluates to 1 + 3, or 4
  6. As a result of 5, y = w + y++ assigns 4 to y.

BUT...

As I said above, this behavior is not guaranteed by the language; except in a few specific cases, the order of evaluation within an expression is left unspecified, so y being 4 is only one of many possible results, all of which are considered equally correct as far as the langauge is concerned.

The behavior of any expression that takes on the following forms (both ++ and --, both prefix and postfix):

x++ + x++   
a[i++] = i
y = y++

is undefined. Similarly, the behavior of statments like

foo(y++, y++);

is also undefined.

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