5

Disclaimer: I don't code like this, I'm just trying to understand how the c language works!!!!

The output is 12.

This expression (a-- == 10 && a-- == 9) evaluates left-to-right, and a is still 10 at a-- == 10 but a is 9 for a-- == 9.

1) Is there a clear rule as to when post-increment evaluate? From this example it seems it evaluates prior to the && but after the ==. Is that because the && logical operator makes a-- == 10 a complete expression, so a is updated after it executes?

2) Also for c/c++, certain operators such as prefix decrement occur right to left so a == --a first decrements a to 9 and then compares 9 == 9. Is there a reason for why c/c++ is designed this way? I know for Java, it's the opposite (it's evaluates left to right).

#include <stdio.h>

int main() {
    int a = 10;
    if (a-- == 10 && a-- == 9)
        printf("1");
    a = 10;
    if (a == --a)
        printf("2");
    return 0;
}
rapidDev
  • 109
  • 2
  • 13
  • [Operator precedence](https://en.cppreference.com/w/c/language/operator_precedence). – KamilCuk Oct 10 '18 at 19:49
  • 1
    That's not logical AND, it's bitwise. Logical AND is `&&` – Tormund Giantsbane Oct 10 '18 at 19:49
  • & is the binary operation, you probably meant && –  Oct 10 '18 at 19:49
  • Sorry, I fixed it. But the same question still holds. – rapidDev Oct 10 '18 at 19:50
  • `(a-- == 10 & a-- == 9)` why would you write code like that? – Jean-François Fabre Oct 10 '18 at 19:50
  • 5
    The side-effects of increments and decrements are complete at sequence points, and not before. The `&&` and `||` operators provide sequence points; the `&` and `|` operators do not. – Jonathan Leffler Oct 10 '18 at 19:50
  • Why would you want to put anyone stuck having to read your code through this. It's not an obfuscation contest. Make it obvious. And that's a binary operator (`&`), not a logical one (`&&`) – Jonathan Wood Oct 10 '18 at 19:50
  • `a-- ==10 & a-- == 9`? Which one do you think will be computed by the compiler first? – Gaurav Oct 10 '18 at 19:51
  • Note that `if (a == --a)` is undefined behaviour — there's no point in speculating about what will happen. The first condition is (now) defined; it should print `1`. You'd do better with newlines after your printing. – Jonathan Leffler Oct 10 '18 at 19:51
  • Good answer. @JonathanLeffler – rapidDev Oct 10 '18 at 19:54
  • 1
    No! This question is not "duplicate" to question on undefined behavior. There is NO any UB because C standard says about `logical and` operator `"guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand."`. So that, all side effects of `a-- == 10` are complete before `a-- == 9` evaluation. This is jet another case. – ReAl Oct 10 '18 at 20:01
  • 1
    @Observe: Neither is compiled by the compiler first. In a modern compiler, the expression is parsed and converted into an internal representation, which may be some sort of rich data structure. This data structure is transformed by various code in the compiler, including optimization, with results that are effectively unpredictable in general. Then the result is used to generate some internal code which is then used to generate assembly code. Assuming recognizable parts of the expression remain, any part of it could be first in the assembly code. – Eric Postpischil Oct 10 '18 at 20:02
  • @EricPostpischil Post See comment: "Sorry, I fixed it. But the same question still holds" above. Misprint is in "&& -> &" but [not in word "logical"](https://stackoverflow.com/posts/52747724/revisions) (see edit 2) – ReAl Oct 10 '18 at 20:07
  • Sorry, I missed the & in my description. It's &&!!!! It's fixed now – rapidDev Oct 10 '18 at 20:08
  • The answer I was looking for was "sequence points". I just wanted a name to the concept I was observing in the code. – rapidDev Oct 10 '18 at 20:19

4 Answers4

3

The logical && operator contains a sequence point between the evaluation of the first and second operand. Part of this is that any side effect (such as that performed by the -- operator) as part of the left side is complete before the right side is evaluated.

This is detailed in section 6.5.13p4 of the C standard regarding the logical AND operator:

Unlike the bitwise binary & operator, the && operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares equal to 0, the second operand is not evaluated.

In the case of this expression:

(a-- == 10 && a-- == 9)

The current value of a (10) is first compared for equality against 10. This is true, so the right side is then evaluated, but not before the side effect of decrementing a that was done on the left side. Then, the current value of a (now 9) is compared for equality against 9. This is also true, so the whole expression evaluates to true. Before the next statement is executed, the side effect of decrementing a that was done on the right side is done.

This expression however:

if (a == --a)

Involves reading and writing a in the same expression without a sequence point. This invokes undefined behavior.

dbush
  • 205,898
  • 23
  • 218
  • 273
1

This expression (a-- == 10 && a-- == 9) evaluates left-to-right,

Yes, mostly, but only because && is special.

and a is still 10 at a-- == 10

Yes, because a-- yields the old value.

but a is 9 for a-- == 9.

Yes, because the sequence point at && guarantees the update to a's value is complete before the RHS is evaluated.

1) Is there a clear rule as to when post-increment evaluate?

The best answer, I think, is "no". The side effects due to ++ and -- are completed at some point prior to the next sequence point, but beyond that, you can't say. For well-defined expressions, it doesn't matter when the side effects are completed. If an expression is sensitive to when the side effect is completed, that usually means the expression is undefined.

From this example it seems it evaluates prior to the && but after the ==. Is that because the && logical operator makes a-- == 10 a complete expression, so a is updated after it executes?

Basically yes.

2) Also for c/c++, certain operators such as prefix decrement occur right to left

Careful. I'm not sure what you mean, but whatever it is, I'm almost certain it's not true.

so a == --a first decrements a to 9 and then compares 9 == 9.

No, a == --a is undefined. There's no telling what it does.

Is there a reason for why c/c++ is designed this way?

Yes.

I know for Java, it's the opposite (it's evaluates left to right).

Yes, Java is different.


Here are some guidelines to help you understand the evaluation of C expressions:

  1. Learn the rules of operator precedence and associativity. For "simple" expressions, those rules tell you virtually everything you need to know about an expression's evaluation. Given a + b * c, b is multiplied by c and then the product added to a, because of the higher precedence of * over +. Given a + b + c, a is added to b and then the sum added to c, because+ associates from left to right.

  2. With the exception of associativity (as mentioned in point 1), try not to use the words "left to right" or "right to left" evaluation at all. C has nothing like left to right or right to left evaluation. (Obviously Java is different.)

  3. Where it gets tricky is side effects. (When I said "'simple' expressions" in point 1, I basically meant "expressions without side effects".) Side effects include (a) function calls, (b) assignments with =, (c) assignments with +=, -=, etc., and of course (d) increments/decrements with ++ and --. (If it matters when you fetch from a variable, which is typically only the case for variables qualified as volatile, we could add (e) fetches from volatile variables to the list.) In general, you can not tell when side effects happen. Try not to care. As long as you don't care (as long as your program is insensitive to the order in which side effects matter), it doesn't matter. But if your program is sensitive, it's probably undefined. (See more under points 4 and 5 below.)

  4. You must never ever have two side effects in the same expression which attempt to alter the same variable. (Examples: i = i++, a++ + a++.) If you do, the expression is undefined.

  5. With one class of exceptions, you must never ever have a side effect which attempts to alter a variable which is also being used elsewhere in the same expression. (Example: a == --a.) If you do, the expression is undefined. The exception is when the value accessed is being used to compute the value to be stored, as in i = i + 1.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
0

With "logical and" operator (a-- == 10 && a-- == 9) is well-formed expression (without undefined behavior as it is in a++ + a++).

C standard says about "logical and"/"logical or" operators:

guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand.

So that, all side effects of the first subexpression a-- == 10 are complete before the second subexpression a-- == 9 evaluation. a is 9 before evaluation of second subexpression.

ReAl
  • 1,231
  • 1
  • 8
  • 19
  • At the moment, this only addresses the first of the two `if` statements (but it does so correctly, given the edited question). – Jonathan Leffler Oct 11 '18 at 23:10
  • Agree, in the second `if` there is undefined order of `==` operands evaluation, so that the operator result also undefined. – ReAl Oct 11 '18 at 23:29
-1

The underlying problem is that the postfix unary operator has both a return value (the starting value of the variable) and a side effect (incrementing the variable). While the value has to be calculated in order, it is explicit in the C++ specs that the sequencing of any side effect relative to the rest of the operators in a statement is undefined, as long as it happens before the full expression completes. This allows compilers (and optimizers) to do what they want, including evaluating them differently on different expressions in the same program.

From the C++20 code spec (C++ 2020 draft N4849 is where I got this from):

Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated. [6.9.1 9, p.72]

If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent, the behavior is undefined. [6.9.1 10, p.72]

So, in case you haven't gotten it from other answers:

  1. No, there is no defined order for a postfix operator. However, in your case (a-- == 10 && a-- == 9) has defined behavior because the && enforces that the left side must be evaluated before the right side. It will always return true, and at the end, a==8. Other operators or functions such as (a-- > a--) could get a lot of weird behavior, including a==9 at the end because both prefix operators store the original value of a(10) and decrement that value to 9 and store it back to a.

  2. Not only is the side-effect of setting a=a-1 (in the prefix operator) unsequenced with the rest of this expression, the evaluation of the operands of == is also unsequenced. This expression could:

    • Evaluate a(10), then evaluate --a(9), then == (false), then set a=9.

    • Evaluate --a(9), then a(10), then set a=9, then evaluate == (false).

    • Evaluate --a(9) the set a=9, then evaluate a(9), then evaluate == (true)

Yes, it is very confusing. As a general rule (which I think you already know): Do not set a variable more than once in the same statement, or use it and set it in the same statement. You have no idea what the compiler is going to do with it, especially if this is code that will be published open source, so someone might compile it differently than you did.


Side note:

I've seen so many responses to questions about the undefined behavior of postfix operators that complain that this "undefined behavior" only ever occurs in the toy examples presented in the questions. This really annoys me, because it can and does happen. Here is a real example of how the behavior can change that I had actually happen to me in my legacy code base.

result[ctr]=source[ctr++];
result[ctr++]=(another calculated value without ctr in it);

In Visual Studio, under C++14, this evaluated so that result had every other value of source in even indexes and had the calculated values in the odd indexes. For example, for ctr=0, it would store source[0], copy the stored value to result[0], then increment ctr, then set result[1] to the calculated value, then increment ctr. (Yes, there was a reason for wanting that result.)

We updated to C++20, and this line started breaking. We ended up with a bad array, because it would store source[0], then increment ctr, then copy the stored value to result[1], then set result[1] to the calculated value, then increment ctr. It was only setting the odd indexes in result, first from source then overwriting the source value with the calculated value. All the odd indexes of result stayed zero (our original initialization value).

Ugh.

clnoel
  • 22
  • 5
  • 1
    “A different compiler might wait to do both post-fix operators until after it has calculated the `&&`” is not allowed by the C++ standard. C++ 2020 draft N4849 7.6.14 2 says, of the two expressions that are operands to `&&`, “… If the second expression is evaluated, the first expression is sequenced before the second expression (6.9.1).” This makes the side effects and value computations in the expressions sequenced. – Eric Postpischil Aug 30 '22 at 16:55
  • I disagree. Operands are not "full-expressions", just expressions. Only unevaluated operands are "full-expressions" (N4849 6.9.1 5), and side effects are only guaranteed to be completed after a full-expression (N4849 6.9.1 9). I'm updating my post to add the exact references for my two quotes. – clnoel Aug 30 '22 at 18:27
  • The rule you cited as 6.1.9 9 (it is actually 6.9.1 9) specifies sequencing for full expressions in general. The rule I cited as 7.6.14 2 **adds** a further sequencing rule for the expressions that are operands of `&&`. The rule in 7.6.14 2 does not require that the expressions be full expressions, just that they be operands to `&&`. This is well known and is a major feature of `&&` that would break many programs were it not true. – Eric Postpischil Aug 30 '22 at 18:37
  • @eric I'm sorry. You're right. I reread 6.9.1 8, which says "sequenced before" means both values and side-effects are done, and the && sequences the left before the right. My extreme example wouldn't actually happen due to the use of &&. I think I'm going to go back and fix up my answer. Thank you for catching my mistake. – clnoel Aug 30 '22 at 18:49