Customers not getting the widgets they paid for if they click too fast -or- In C#, the += operator is not merely not guaranteed to be atomic, it is guaranteed not to be atomic


In the C# language, operation/assignment such as += are explicitly not atomic. But you already knew this, at least for properties.

Recall that properties are syntactic sugar for method calls. A property declaration

string Name { get { ... } set { ... } }

is internally converted to the equivalent of

string get_Name() { ... }
void set_Name(string value) { ... }

Accessing a property is similarly transformed.

// o.Name = "fred";
o.put_Name("fred");

// x = o.Name;
x = o.get_Name();

Note that the only operations you can provide for properties are get and set. There is no way of customizing any other operations, like +=. Therefore, if you write

o.Name += ", Jr.";

the compiler has no choice but to convert it to

o.put_Name(o.get_Name() + ", Jr.");

If all you have is a hammer, everything needs to be converted to a nail.

Since the read and write are explicitly decoupled, there is naturally a race condition here. The underlying property may change value in between the time you read the old value and the time you write the new value.

But there are extra subtleties here. Let's dig in.

The rule for operators like += are spelled out in Section 14.3.2: Compound Assignment:

[T]he operation is evaluated as x = x op y, except that x is evaluated only once.

(There is some discussion of what "evaluated only once" means, but that's not important here.)

The subtleties lurking in that one sentence are revealed when you see how that sentence interacts with other rules in the language.

Now, you might say, "Sure, it's not atomic, but my program is single-threaded, so this should never affect me."

Actually, you can get bitten by this even in single-threaded programs. Let's try it:

class Program
{
 static int x = 0;

 static int f()
 {
  x = x + 10;
  return 1;
 }

 public static void Main()
 {
  x += f();
  System.Console.WriteLine(x);
 }
}

What does this program print?

You might naïvely think that it prints 11 because x is incremented by 1 by Main and incremented by 10 in f.

But it actually prints 1.

What happened here?

Recall that C# uses strict left-to-right evaluation order. Therefore, the order of operations in the evaluation of x += f() is

  1. Rewrite as x = x + f().
  2. Evaluate both sides of the = operator, left to right.
    1. Left hand side of assignment: Find the variable x.
    2. Right hand side of assignment:
      1. Evaluate both sides of the + operator, left to right.
        1. Evaluate x.
        2. Evaluate f().
      2. Add together the results of steps 2b(i)1 and 2b(i)2.
  3. Take the result of step 2b(ii) and assign it to the variable x found in step 2a.

The thing to notice is that a lot of things can happen between step 2b(i)1 (evaluating the old value of x), and step 3 (assigning the final result to x). Specifically, we shoved a whole function call in there: f().

In our case, the function f() also modifies x. That modification takes place after we already captured the value of x in step 2b(i)1. When we get around to adding the values in step 2b(ii), we don't realize that the values are out of date.

Let's step through this evaluation in our example.

  1. Rewrite as x = x + f().
  2. Evaluate both sides of the = operator, left to right.
    1. Left hand side of assignment: Find the variable x.
    2. Right hand side of assignment:
      1. Evaluate both sides of the + operator, left to right.
        1. Evaluate x. The result is 0.
        2. Evaluate f(). The result is 1. It also happens that x is modified as a side-effect.
      2. Add together the results of steps 2b(i)1 and 2b(i)2. In this case, 0 + 1 = 1.
  3. Take the result of step 2b and assign it to the variable x found in step 2a. In this case, assign 1 to x.

The modification to x that took place in f was clobbered by the assignment operation that completed the += sequence. And this behavior is not just in some weird "undefined behavior" corner of the language specification. The language specification explicitly requires this behavior.

Now, you might say, "Okay, I see your point, but this is clearly an unrealistic example, because nobody would write code this weird."

Maybe you don't intentionally write code this weird, but you can do it accidentally. And this is particularly true if you are using the new await keyword, because an await means, "Hey, like, put my function on hold and do other stuff for a while. When the thing I'm awaiting is ready, then resume execution of my function." And that "do other stuff for a while" might change x.

Suppose that you have a button in your application called Buy More. When the user clicks it, they can buy more widgets. Let's assume that the Buy­More­Async function return the number of items bought. (If the user cancels the purchase it returns zero.)

// Suppose the user starts with 100 widgets.

async void BuyMoreButton_OnClick()
{
 TotalWidgets += await BuyMoreAsync();

 Inventory.Text = string.Format("You have {0} widgets.",
                                TotalWidgets);
}

async Task<int> BuyMoreAsync()
{
 int quantity = QuickPurchase.IsChecked ? 1
                                        : await GetQuantityAsync();
 if (quantity != 0) {
  if (await DebitAccountAsync(quantity * PricePerWidget)) {
   return quantity;
  }
 }
 return 0; // user bought no items
}

You receive a bug report that you track back to the fact that Total­Widgets does not match the number of widgets purchased. It affects only people who checked the quick purchase box, and only people purchasing from overseas.

Here's what is going on.

The user clicks the Buy More button, and they have Quick Purchase enabled. The Buy­More­Async function tries to debit the account for the price of one widget.

While waiting for the server to process the transaction, the user gets impatient and clicks Buy More a second time. This triggers a second task to debit the account for the price of one widget.

Okay, so you now have two tasks running, each processing one of the clicks. In theory, the worst case is that the user accidentally buys two widgets, but in practice...

The first Debit­Account­Async task completes, and Buy­More­Async returns 1, which is then added to the value of Total­Widgets at the time the button was clicked, as we discussed above. At the time the button was clicked the first time, the number of widgets was 100, so the total number of widgets is now 101.

The second Debit­Account­Async task completes, and Buy­More­Async returns 1, which is then added to the value of Total­Widgets at the time the button was clicked, as we discussed above. When the button was clicked the second time, the number of widgets was still 100. We set the total widget count to 100 + 1 = 101.

Result: The user paid for two widgets but got only one.

The fix for this is to explicitly move waiting for the purchase to complete outside of the compound assignment.

 int quantity = await BuyMoreAsync();
 TotalWidgets += quantity;

Now, the await is outside the compound assignment so that the value of Total­Widgets is not captured prematurely. When the purchase completes, we update Total­Widgets without interruption from any async operations.

(You probably also should fix the program so it disables the Buy More button while a transaction is in progress, to avoid the impatient user ends up making an accidental double purchase problem. The above fix merely gets rid of the user pays for two items and gets only one problem.)

Like closing around the loop control variable, this is the sort of subtle change that should be well-commented so that somebody doesn't "fix" it in a well-intentioned but misguided attempt to remove unnecessary variables. The purpose of the variable is not to break an expression into two but rather to force a particular order of evaluation: You want to to finish the purchase operation before starting to update the widget count.

Comments (40)
  1. Zarat says:

    And let me guess, nobody is going to fix the evaluation order of "x += f()" to first evaluate the right hand side, then capture the variable, because it'd be a "breaking change". Even though everyone affected is probably broken to begin with.

  2. 12BitSlab says:

    @ Zarat

    To say "fix", one must assume that the evaluation order is somehow broken.  It isn't.  It is 100% in keeping with the public spec for C#.  Just because developers fai to read the docs, doesn't mean that something is broken and needs to be fixed.

  3. Jeff says:

    Maybe they should fix the spec so that they can then fix the evaluation order ;)

  4. JW says:

    @ Zarat & 12BitSlab:

    As 12BitSlab says, it is not broken. It just looks like a broken concept because we're 'zoomed in' on this particular case of C# expressions.

    Suddenly saying 'this case is special and should have right-to-left evaluation' would make even less sense. To begin with, would that only apply to stuff in the shorthand '+=' format? Or would it somehow need to be sniffed out that the first argument of the expression is the same as what comes before the assignment operator? Sure sure, you might say, you can just avoid all that mess by instead changing 'x += 5' behaviour into 'x = 5 + x'… but then you could probably get into nasty territory involving operator overloading and implicit conversions which would lead to even harder to figure out problems.

    Making this case 'sensible' will make a lot of other cases confusing and hard to understand warts of the language specification. It simply isn't worth it, even assuming that this change would not break anything.

  5. Mc says:

    Just tested and the same thing applies to VB.NET .   Eye opening for sure.

  6. Andy says:

    For instance, changing x += 5 to mean x = 5 + x would be disastrous for operations for which addition is not associative, like o.Name += ", Jr.";

  7. Andy says:

    And for associative of course read commutative…

  8. ipoverscsi says:

    @Zarat: As you pointed out, the '+=' operator is typically syntactic sugar that gets translated at compile-time;  thus, x += 5 is translated into x = x + 5.

    While the assignment operator '=' is processed in right-to-left order, most binary operators are processed in left-to-right order, including the '+' operator.In order for your suggestion to work, you would need a new operator called 'special-plus' that is performs the same action as the '+' operator but it evaluates in right-to-left order. In this case, x += 5 would be translated into x = x 'special-plus' 5. Note that we can't reverse the argument order (as you mentioned) otherwise 'string x += "suffix"' won't work.

    For each possible compound operation ('-=', '*=', …) you would need a special opcode that does the same thing as the regular opcode but with right-to-left evaluation.

    This is actually a lot of work that will lead to compiler complexity and errors. This from a guy who's actually written a few compilers in his time.

  9. KC says:

    @Zarat, it's not a question of when the left hand side gets evaluated.  x += f() gets rewritten as x = x + f().  The "x + f()" part gets evaluated left to right.

     KC

  10. Deduplicator says:

    @Zarat: I commiserate with you, there are too many literalists who seem to have one-track minds.

    To reformulate your question:

    Is there any reason (beside it having been done so), that "x += y;"

    is evaluated as "x = x + y;"

    instead of "{var temp = y; x = x + temp;}",

    with in any case x only being evaluated once?

    And if there is none, is there good enough reason not to correct the spec and the language itself?

    Is there credible reason to think any non-broken code will change its meaning?

  11. Zarat says:

    I don't know what you are trying to explain/defend, you kinda claim that its impossible to fix this issue, but that's simply not true.

    Compilers can do more than just expression rewriting, there is nothing (besides the spec, which can also be fixed) that would prevent a compiler to push the result of "f()" (or whatever the right hand side of "x += …" is) on the stack (in case of .NET IL a local slot may be better than the stack), call the getter for x, call "operator +" with the right argument order, then call the assignment operator. No special opcodes needed, just redefined when exactly the getter for x is called (after evaluating the right hand side instead of before).

    And before someone complains that would make IL larger by a byte or two, I'm aware of that and still think it'd be worth to fix – mostly due to the async/await use case – your opinion may of course differ.

  12. Zarat says:

    @Deduplicator: Thanks for clarifying, that's exactly what I meant (didn't think about formulating it as a rewritten expression though, nice one)

  13. Frank says:

    Interesting enough, I tried the code in C++. I got "11" as the result. Looking at the compiled (assembly) code, C++ evaluates the function first before capturing "X".

  14. dave says:

    >C++ evaluates the function first before capturing "X".

    Or more exactly, a particular implementation of C++ on this particular source code on this particular day of the week happened to evaluate the function first.

  15. Christiaan Rakowski says:

    Interesting stuff. Will definitely check some places in our codebase for this!

  16. Deduplicator says:

    @SimonRev: What's your definition of "the language is broken"?

    Imho, C# acts counter-intuitivels there, due to how the compund-assignment-operators are defined. If that's not a defect in the language, what would be? I concede that C++ has no strong left-to right ordering for expression evaluation (citing another language which in another situation has weaker guarantees is at best a strawman anyway, especially as that's somewhat expected), but at least it does not ever explode a compound-assignment operator behind your back.

    @ChrisB: You are missing the point, which is that the compound-assignment-operators have a bad (aka counter-intuitive) definition. It has nothing at all to do with strict left-to-right ordering.

    @Matt: Putting up a big warning is better than nothing, hope that will be implemented at minimum.

  17. Zan Lynx&#39; says:

    @dave: Check out the C++11 sequencing rules. They've changed since C++03 and become better defined. C++ will guarantee that the functions have been run and results returned before using those results in another operation. And it also promises that the right-hand side of an assignment operation is complete before performing the assignment.

  18. meh says:

    If C# wants to play with the big boys and include the += etc operators shouldn't it have matched what C does? Otherwise you get code that would just do the x += y assignment on one line, but then has to split it apart like a two year-old on the next line (for the await reason). They've changed the spec before for odd things (blogs.msdn.com/…/closing-over-the-loop-variable-considered-harmful.aspx). What does the introduction of the await-type thing-o in the new Visual C++ do? If it matches what the CLR spec does then maybe that might be ok; but if not then wtf?

  19. Zarat says:

    I wasn't suggesting to change x += 5 to mean x = 5 + x … I was saying to change the *evaluation* order (of the expression) not the *argument* order (of the operator).

    For an assignment expression of the style "x += f()" it makes perfect sense to evaluate the right hand side before the left hand side, in fact I think it makes *more* sense than the presented behavior. It probably just ended up with the bugged way we have because it is specced (and probably implemented) via expression rewriting.

    IMHO I think it's worth fixing, but I doubt they will.

    @12BitSlab (and others "defending" the current behavior): Just because it has been specced this way, that doesn't mean it has to be the right way. Can anyone actually give an example where this evaluation order makes sense or is useful? ;)

  20. Mike Fisher says:

    The "fixed" code still has the exact same problem as before. The race condition is still there. It is just that the race condition is much less likely to be hit now because there is only a short delay between reading and writing the TotalWidgets property since the expensive BuyMoreAsync call was moved to before reading the property instead of after.

    The real solution is to modify the TotalWidgets property in an atomic way. You could add a method called AddWidgets that internally uses Interlocked.Add (msdn.microsoft.com/…/33821kfh.aspx) on the field. Alternatively, the += operation could be done inside a lock.

    It is good to remember that += is not atomic. If it is going to be called on multiple threads in parallel, you have to use Inetrlocked or some sort of synchronization (lock).

    In a way it is good that your code was written in a way that exposed the race condition. If you use the "fixed" code, it still has the problem but it would be harder to track down due to occurring only rarely.

    [We are assuming single-threaded operation throughout, so nothing can run between reading and writing the TotalWidgets property. (This was implied because this is UI code, and UI objects can be accessed only from the thread that created them. Therefore, all the code in all the examples run on the same thread.) The point is that the async keyword suspends execution of the task and allows another task to run on that thread. And that other task may interfere with your computation. -Raymond]
  21. glugglug says:

    In other languages like C++ and Java, the = and related operators (like +=) are explicitly right associative, not left, so this evaluates as you would expect.

    Also the = or += operator returns a reference to the left hand result, so, in C++

    x += y += z; is the same as

    x += (y += z); or

    y = y + z; x = x + y;

    Given C# is also a C-oid language, this pure left to right evaluation seems really broken.

    [You've confused associativity with order of evaluation. The C# assignment operators are right-associative but evaluate left-to-right. The C assignment operators are also right-associative, but evaluation order is unspecified. -Raymond]
  22. Peter Crabtree says:

    @Deduplicator/@Zarat

    I agree thoroughly. There's no good reason for C# to behave this way for the compound operators, and when combined with await, this results in some seriously unexpected behavior.

    Any code of the form x += y; where the get for x needs to be evaluated for the value of y to be correctly evaluated is very probably already broken, and at best is working by sheer luck.

    At the very least, a compilation-time warning is called for.

  23. Cheong says:

    Somehow I have the thought that today's article is inspired by G's wallet. My sister was charged triple times for buying single article.

  24. SimonRev says:

    @Dedup:

    I don't see a reason to change the language.  As several have pointed out, the language itself isn't broken, rather doesn't behave as intuitive as you might like for this *specific* example.  Keep in mind that when evaluating an expression in the form x+=y, that *y* can just as easily have side effects as evaluating x!  We can easily rewrite the example so that y is a property which modifies the static variable in which case right to left evaluation seems wrong.

    Just be glad we aren't in C++ land where not only is evaluation order not specified, but it is entirely possible and reasonably for a compiler for the expression a=b+c to evaluate part of c, then part of b then the rest of c then the rest of b before performing the addition — talk about a headache there.  In fact, I think that C++ could choose to evaluate 'a' right in the middle there if it felt like.

  25. Chris B says:

    @Deduplicator/@Zarat

    I imagine strict left to right ordering was chosen (at least in part) so that all compilers execute code in the same order. For a statement like

    int x = y() / z();

    C/C++ compilers are permitted to call y() and z() in the order of their choosing.  This becomes important when one of the functions depends on a side effect of the other.

    In C#, this is important because x could be a property, which as Raymond mentioned, is just short hand for a method called get_x(); If the get_x() pseudo-method has a side effect, it is reasonable for a developer to expect that it be observed only once (since x only appears once), and that it occur *before* y is evaluated (since x comes before y).

  26. Matt says:

    Might be worth VS team adding a warning for the following rule:

    For every sub-expression in the AST:

    * If the expr is a compound assignment (i.e. %=, /=, *=, +=, -=, ^=, |=, &=)

    * AND the right-hand side of the operation CONTAINS an AWAIT-expression (that definitely terminates)

    * AND the left-hand side of the operation is Not

     * A local-variable or a parameter to the function

     * And also a value type

    * Then:

    Raise a warning in the compiler for the developer, i.e: "The read and write parts of a compound-assignment expression are potentially delayed by an "await" statement, which could lead to a race-condition".

  27. Peter says:

    >Interesting enough, I tried the code in C++.

    Frank, you're going to have to be a lot more specific than that: what compiler, what platform, what version, etc… since the spec doesn't say what order the expression must be evaluated in, each compiler could do it differently.  Or the same compiler could do it differently based on what the target architecture is.

  28. voo says:

    @Deduplicator and co: So you're saying that interpreting "x += y" as "x = x + y" is counter intuitive? I disagree!

    This comes down to simple preferences though: On one hand we have purists that prefer a simple, easy to understand system which means they have to learn a few rules and can then reason out the rest. On the other hand we have people who prefer to introduce special rules for things to make them more "intuitive" or easier (except in rare situations where all those extra, special rules start to interact in weird and confusing ways).

    This is basically Lisp vs. Basic.

  29. dave says:

    >since the spec doesn't say what order the expression must be evaluated in,

    The spec goes further: it says the order is undefined. That is, the order is explicitly not specified, rather than merely unspecified due to omission.

    (A minor wording quibble, perhaps, but it indicates the spirit better: even if you can find out what your compiler does today, you'd be a fool to rely on it)

  30. Chris B says:

    @Deduplicator:

    "Is there any reason (beside it having been done so), that 'x += y;'

    is evaluated as 'x = x + y;'

    instead of '{var temp = y; x = x + temp;}',

    with in any case x only being evaluated once?"

    And my response is that x is evaluated first because it precedes y in the statement, which is strict left-to-right ordering. Your example is right to left ordering. What point am I missing?

  31. Deduplicator says:

    @voo: Sorry, but you are completely off your rocker.

    That has nothing to do with adhering to strict left-to-right evaluation-order, but with how op= is defined to make proper sense.

    And if you go the route that whatever way it is, that's natural and good and glorious, why don't you loudly decry the fix to range-based loops and closure?

    @ChrisB: The point you are missing is that (as Raymond acknowledges), the definition for op= in C# is at best unfortunate, and arguably and error. What exactly that definition is or should be has exactly nothing at all to do with evaluation-order.

    Anyway, up to now nobody at all made the case for there being any non-broken code which would change its meaning due to fixing that trap. Though there were plenty who defended the strawman that the definition of op= is somehow unavoidable and sacrosanct due to evaluation-order.

    [I have a case for non-broken code relying on order of evaluation, but the discussion is too big to fit into a comment, so you'll have to wait until it reaches the head of the queue. -Raymond]
  32. ls says:

    @Deduplicator:

    > Is there any reason (beside it having been done so), that "x += y;"

    > is evaluated as "x = x + y;"

    Because it's straightforward and makes the code easy to reason about? Saying "x += y is a shorthand for x = x + y, except that x only gets evaluated once" is a sane thing to do. It's the way almost every undergrad programming course explains those operators. If you fiddle around with the evaluation order, your simple rule is gone. Mayhem results.

  33. MBR says:

    This behavior seems completely unsurprising and reasonable.

    Even if it were not, I don't think the compiler should be trying to "fix up" code that tries to mix the global side-effects f() and the value returned by f() – this is just rotten code – it's basically like doing:

      x = f(ref x);

    – I'd expect the assignment to clobber any changes to x made inside of f() – but I never count on it either.

  34. Adam Rosenfield says:

    [I have a case for non-broken code relying on order of evaluation, but the discussion is too big to fit into a comment, so you'll have to wait until it reaches the head of the queue. -Raymond]

    I will patiently await the article on this subject in summer 2016 or so.

  35. KallDrexx says:

    @Dedup

    "Anyway, up to now nobody at all made the case for there being any non-broken code which would change its meaning due to fixing that trap. Though there were plenty who defended the strawman that the definition of op= is somehow unavoidable and sacrosanct due to evaluation-order."

    You missed the argument about o.Name += ", Jr" above.  That will be completely broken in changing the order of evaluation, and either requires users to do ", JR" += o.Name (which is completely unintuitive for an append operation).

    It also means that when you create a custom class that overrides the += you have to know that it has to be defined as:

    public static MyClass operator += (MyClass right, MyClass left).  Changing this operation not only risks breaking almost all overloaded operators on += it makes it far less intuitive when you need to overload those operations and the only reason anyone is thining that this isn't intuitive is because of this one edge case, but once you reverse the evaluation logic you have edge cases that will cause you to go "this is retarded, it should be re-reversed".

  36. IanBoyd says:

    I imagine *a* reason for existing: is the ability to opt into an optimization.

    The value of the variable is pushed onto the stack as `value1`. The value to add is computed and pushed onto the stack as `value2`.

    Then you can call the IL `add` instruction:

       add – add numeric values

       58   add

       Add two values, returning a new value.

       Stack Transition:

       …, value1, value2  …, result

       The add instruction adds value2 to value1 and pushes the result on the stack

    If you didn't want the value of x pushed onto the CLI stack at the start of the instruction and left there, you should have used:

       x = x + 1

    instead.

    (and before hit post, i recognize a 10% chance that my formatting will look readable in the rendered form)

  37. Deduplicator says:

    @Raymond: Eagerly awaiting it. Even though there is then at least one example for code which would actually break if the semantics were fixed. Still, in the case of closures and the ranged-for loop, there probably were some too, so it's not categorically the death-knell to any chance of a fix.

    @KallDrexx: Seems you didn't read anything of what I wrote. Because your post does not in the least reflect what I said. Makes me sad that so few seem to take the time to read, before they jump to the defense of orthodoxy like well-trained attack-dogs.

  38. KallDrexx says:

    @Deduplicator

    I guess I was getting your arguments mixed up with Zatar's, my bad.

    However, re-reading your argument it seems like you are questioning why it's always done that way instead of another way, such as with a temp variable behind the scenes. I'd argue that your idea of using temp tables isn't a good idea either.  This means you are now taking something that's a minimum of 2 operations and now forcing it to be a minimum of 3.  While it may not seem that huge, it can be compounded in loops and cause slightly worse performance in a hard to predict way (since most people are expecting it to be straight forward x = x + y).

    Furthermore, if you are dealing with reference types instead of value types you are now adding extra garbage that has to be cleaned up.  That will significantly impact any games or high performance applications.

    Other than those two I'm not sure of how you are going to do it without adding even more complexity in an area that really shouldn't be that complex for no reason other than to hopefully try and save coders from themselves, and I'd argue that the edge case in this article that makes op= not function as predicted is more of a an argument for reducing side effects of functions instead of trying to save the coder from it themselves.

  39. Nico says:

    I was just browsing Stack Overflow's "Hot Questions" when I came across this: stackoverflow.com/…/c-and-php-vs-c-sharp-and-java-unequal-results

    I'm not sure why the guy decided to effectively copy and paste your post (and without any kind of credit to you) and then ask a question which you directly answered, but there it is.  Maybe a lame attempt at some easy internet points.

    Regardless, an interesting topic and another enjoyable CLR week. Thanks, Raymond.

    ["I've made the big time. I'VE BEEN STOLEN!" -Raymond]
  40. John Doe says:

    > There is some discussion of what "evaluated only once" means, but that's not important here.

    (It would have been easier to copy-paste the spec following this statement, it's actually simpler, but…)

    Actually, there are two important aspects here:

    – x is any expression, which is stated nowhere

    – what is evaluated only once are the sub-expressions that lead to the variable reference (lvalue) expression

     – e.g. a very simple example, in Object.Property.Method(argument).Field += <whatever>

     – Object, Property, argument and Method() are evaluated only once

     – then Field is evaluated for its value, then <whatever> is evaluated, then the operation (+) is evaluated, then Field is set with the resulting value.

    So,

    Object.Property.Method(argument).Field += <whatever>;

    is roughly equivalent to

    var __temp = Object.Property.Method(argument);

    __temp.Field = __temp.Field + <whatever>;

    where temporary variables are used as needed due to a method call, a property access or array-like indexing.  If x is already a variable or field reference, there's no need for the temp.

    You need as many temporary variables as the necessary sub-expressions of the l-value expression, e.g. Object.Property.Method(argument).Field[<expr>] would need an extra temporary for the evaluation of <expr>.

    About C/C++, there's a strange case where it guarantees order of evaluation, comma expressions, probably because of the first three parts of the for statement.  An afterthought.

    This is very easily confused with argument list expressions, which don't have a specific order of evaluation.

Comments are closed.