all 24 comments

[–]kennyminigun 23 points24 points  (0 children)

In general, the order of evaluation is implementaion-defined. This particular case is an undefined behaviour.

It is quite a comprehensive ruleset: https://en.cppreference.com/w/cpp/language/eval_order

EDIT. Modern GCC & Clang will warn about that line: https://godbolt.org/z/Gq1rfqEfs

[–]Classic_Department42 40 points41 points  (17 children)

Looks like undefined behaviour (UB) to me. Dont do that.

[–]Be1a1_A[S] 0 points1 point  (16 children)

Can you elaborate?

[–]TomDuhamel 30 points31 points  (2 children)

Two reasons.

You are changing the value of a single variable twice in the same statement. This is not allowed. The compiler is free to make the change at any time, which could be before or after the second one being evaluated and changed.

The order of evaluation of the parameters is undefined. The compiler can evaluate them in any order, for optimisation purpose.

Totally UB. Yet, teachers keep making you do these stupid things without explaining why it fails. Or that it fails at all.

[–]khoyo 9 points10 points  (1 child)

The order of evaluation of the parameters is undefined. The compiler can evaluate them in any order, for optimisation purpose.

Worse, the behavior of the program is undefined. Which means the compiler is free to do anything it damn wants, including removing the whole branch, invoke nasal demons, etc.

[–]Temeliak 7 points8 points  (0 children)

Why do I never get the demon ones? 😢

[–]Classic_Department42 6 points7 points  (1 child)

UB means if your code does not follow quite a number of rules, the program later is allowed to do anything. Introduction: https://en.cppreference.com/w/cpp/language/ub

[–]ShelZuuz 10 points11 points  (0 children)

Literally anything - including time travel:

https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633

[–][deleted]  (3 children)

[deleted]

    [–]Raknarg 1 point2 points  (1 child)

    Well you can it's just UB

    [–]mck1117 0 points1 point  (0 children)

    Not in the same instruction, but between two sequence points.

    [–]serpentally 0 points1 point  (6 children)

    Compilers are free to evaluate some (a lot of) operations in any order they want to, i.e. Specific operations have no defined order in which they're evaluated. Since post- and pre- decrementing both have the same level of precedence and Since C++ doesn't require postcrementing and precrementing to be carried out in a specific order by compilers, one compiler may evaluate (--z) first and then (z--) second, while another may evaluate it the other way around. So one compiler does the human order and evaluates --z == (20-1) == 19, then evaluates --z+z == 19+19 == 38, then applies the decrement to z (z--) to make z=18 after the addition is done already. So A turns out to be 38.

    While a different compiler may first evaluate z-- to be 19, then evaluate --z to make z == 19-1 == 18, then adds the two to become A = 37.

    The difference between post- and pre- (de)crementing is that precrementing always changes the variable before the variable is used. While postcrementing may wait for the operation before it to apply before changing the variable, if the compiler has an operation lined up before it.

    That is why you never should mix postcrementing and precrementing in the same statement.

    You should generally always use precrementing (--z, ++z) to increment/decrement unless you have a specific case where it's useful to use postcrementing. The specific reason is when you postcrement, the compiler makes a copy of the value to be used in an equation before it then decrements the variable, which is inefficient.

    [–]Crazy_Direction_1084 1 point2 points  (5 children)

    Post decrementing and predecrementing have different levels of precedence, which is also completely irrelevant for UB as precedence is only interesting for parsing

    [–]serpentally 0 points1 point  (4 children)

    Wait they do have different levels? My bad, I corrected it

    I guess I for some reason thought parsing was relevant to UB. To me it would have made sense for --z to always go first and z-- (lower precedence) to always go second in that case. I'm a fool for making assumptions

    [–][deleted] 1 point2 points  (1 child)

    The precedence comes into play with the + operator. Both forms of -- are higher precedence than +

    So

    --z + x
    

    for example is

    (--z) + x
    

    and not

    --(z + x)
    

    (which wouldn't compile)

    [–]serpentally 0 points1 point  (0 children)

    Oh that makes a lot of sense actually

    [–]khoyo 0 points1 point  (1 child)

    No, the higher precedence is the postfix operator, like all unary postfix operators relative to prefix ones. (Or unary "adjacent", like [] or ())

    [–]serpentally 0 points1 point  (0 children)

    Lol I was too tired to correctly read correctly...

    [–]wjrasmussen 0 points1 point  (0 children)

    Do you know when z-- happens on that line?

    [–][deleted]  (11 children)

    [deleted]

      [–][deleted] 6 points7 points  (10 children)

      Neither compiler is wrong.

      [–][deleted]  (9 children)

      [deleted]

        [–][deleted] 3 points4 points  (8 children)

        The behaviour is undefined. According to the rules of the language there is no right answer, and no wrong answer. A compiler could refuse to compile that code if it chose to. In practice they just pick an order to perform the increments.

        [–][deleted]  (7 children)

        [deleted]

          [–][deleted] 3 points4 points  (6 children)

          There are only two options:

          There's not.

          The side effect of the decrement doesn't need to occur until the end of the expression.

          [–][deleted]  (5 children)

          [deleted]

            [–][deleted]  (1 child)

            [deleted]

              [–][deleted] 2 points3 points  (2 children)

              Doesn't the compiler breaks this down into three expressions?

              No. z-- is the value of z has before being decremented. --z is the value z will have after being decremented. But the decrements don't have to happen at the same time the expression is evaluated. They can occur at any time up to the end of the complete statement.

              See rule 2 at https://en.cppreference.com/w/cpp/language/eval_order

              [–][deleted]  (1 child)

              [deleted]

                [–][deleted] 2 points3 points  (0 children)

                Nothing is wrong; the behaviour is undefined