What is the order of evaluation in C#?


The C and C++ languages leave the order of evaluation generally unspecified aside from specific locations called sequence points. Side effects of operations performed prior to the sequence point are guaranteed visible to operations performed after it.¹ For example, the C comma operator introduces a sequence point. When you write f(), g(), the language guarantees that any changes to program state made by the function f can be seen by the function g; f executes before g. On the other hand, the multiplication operator does not introduce a sequence point. If you write f() * g() there is no guarantee which side will be evaluated first.

(Note that order of evaluation is not the same as associativity and operator precedence. Given the expression f() + g() * h(), operator precedence says that it should be evaluated as if it were written f() + (g() * h()), but that doesn't say what order the three functions will be evaluated. It merely describes how the results of the three functions will be combined.)

In the C# language, the order of evaluation is spelled out more explicitly. The order of evaluation for operators is left to right. if you write f() + g() in C#, the language guarantees that f() will be evaluated first. The example in the linked-to page is even clearer. The expression F(i) + G(i++) * H(i) is evaluated as if it were written like this:

temp1 = F(i);
temp2 = i++;
temp3 = G(temp2);
temp4 = H(i);
return temp1 + temp3 * temp4;

The side effects of each part of the expression take effect in left-to-right order. Even the order of evaluation of function arguments is strictly left-to-right.

Note that the compiler has permission to evaluate the operands in a different order if it can prove that the alternate order of evaluation has the same effect as the original one (in the absence of asynchronous exceptions).

Why does C# take a much more restrictive view of the order of evaluation? I don't know, but I can guess.²

My guess is that the language designers wanted to reduce the frequency of a category of subtle bugs (in this case, order-of-evaluation dependency). There are many other examples of this in the language design. Consider:

class A {
 void f()
 {
  int i = 1;
  if (true) {
   int i = 2; // error - redeclaration
  }
 }

 int x;
 void g()
 {
  x = 3; // error - using variable before declared
  int x = 2;
 }
}

The language designers specified that the scope of a local variable in C# extends to the entire block in which it is declared. As a first consequence of this, the second declaration of i in the function f() is illegal since its scope overlaps with the scope of the first declaration. This removes a class of bugs that can be traced to one local variable masking another with the same name.

In the function g() the assignment x = 3; is illegal because the x refers not to the member variable but to the local variable declared below it. Notice that the scope of the local variable begins with the entire block, and not with the point of declaration as it would have been in C++.

Nitpicker's Corner

¹This is a simplified definition of sequence point. For more precise definitions, consult the relevant standards documents.

²I have not historically included the sentence "I don't know but I can guess" because this is a blog, not formal documentation. Everything is my opinion, recollection, or interpretation. But it seems that people take what I say to establish the official Microsoft position on things, so now I have to go back and add explicit disclaimers.

Comments (68)
  1. Lauren Smith says:

    So in g(), the x=3 refers to the locally scoped x which doesn’t get created until later so is invalid to use at any point before it is declared? Why scope it that way (backwards to the enclosing block) if you aren’t going to allow use the var anywhere from within that block? And if you aren’t going to allow its use, why wouldn’t you default to allow the currently-valid (if not for the previously mentioned scoping rule) variable?

    I wish I understood it. I’d probably feel a little better about myself now.

  2. Anonymous says:

    I think this is very interesting.

    Moving from native to managed code generally means ceding control to the system to do things like memory management and controlling object lifetimes, so it seems ironic that C# gives you more precise control in the order of evaluation.

    Of course, in C or C++ you CAN control the order when it’s important (by introducing sequence points), but you can also leave it up to the compiler to optimize when it’s not critical.

    I understand the reasoning.  I’m just amused because I find the result the opposite of what I’d expect.

  3. Anonymous says:

    Excerpt

    int x;

    void g()

    {

     x = 3; // error – using variable before declared

     int x = 2;

    }

    The issue is not that you are using a variable before it’s declared, it’s that you are re-declaring ‘x’.

    Fields on a class are automatically given their default value upon instantiation of a class, reference types get null, value types get their default value(0 for numeric, MinDate for DateTime, etc).

  4. Anonymous says:

    Lauren: If you intentionally wrote code like that, anyone who maintains it will despise you since two distinct variables in the same block would have the same name. If you’re reading through the code and don’t notice the new declaration you would assume both x’s refer to the same thing.

    This will also cause you to notice a bug that might be hard to discover otherwise. Suppose you are cutting and pasting code to reorder the behavior of some block of code. If you happen to move the variable declaration below another use of the variable, C# will flag it as an error, even if a variable with the same name was declared in another scope.

  5. Eric Lippert says:

    Lauren: there are two principles here.

    1) Locals must be declared before they are used.

    2) In a legal program, moving a local declaration which does not have a side effect (ie, an initialization) "higher up" in the function should not have a semantic impact.

    Suppose we did it your way, and we had:

    void M() {

    // 30 lines of code

    x = 10;

    // 30 lines of code

    int x;

    // 30 lines of code

    x = 20;

    // 30 lines of code

    }

    A maintenance programmer decides that she wants all the declarations at the top of the method, so she moves the "int x;" up to the top, and suddenly the meaning of the program changes.  That would be bad.  That’s exactly the kind of subtle stupid bug that we’re trying to automatically prevent.

    Look at it this way: is there ever a really compellingly good reason to write code like that above?  I can’t think of any. It seems like a recipe for bugs to me. A fundamental principle of the design of C# is that it leads you to write correct programs.

  6. Lauren Smith says:

    Is there any benefit to giving this error instead of just implicitly moving the variable declaration up to the top of the block and quietly doing what the programmer intends?

    [C# is not JScript. JScript is the “quietly do what the programmer probably meant” language. C# is the “do what the programmer wrote” language. -Raymond]
  7. Anonymous says:

    Raymond, there is a positive side effect to everyone assuming what you write is formal documentation:  If you ever want to force an implementation one way just say it’s implemented like that and MS will be mad but have to comply.

  8. Lauren Smith says:

    The programmer wrote, though, to assign 3 to x, the only x available at that point in time being the class member.

    Eric’s statement "A fundamental principle of the design of C# is that it leads you to write correct programs" makes this type of language rule understandable. It just knocked me for a loop looking at what is seemingly valid code that throws a compiler error.

  9. Anonymous says:

    A question on your example F(i) + G(i++) * H(i): the breakdown implies that if G receives its parameter by reference, it receives not i, but a temporary variable that happens to be equal to i.

    Is this actually the case, or is the assignment to temporary variables simply for clarification?

    If it IS passed a temporary variable, what is the defined behaviour if you attempt to alter that value in G? Is it documented? (If so, you need only say so, and I’ll hopefully come across the answer once my self-edification in C# reaches the point where I feel able to comprehend the formal documentation. I’m still wrapping my mind around the object model.)

    [You didn’t need to ask this question. Don’t be lazy. Think. If G() took a reference parameter, the example wouldn’t compile. -Raymond]
  10. Anonymous says:

    I honestly wasn’t trying to nitpick – I was curious. I apologize, I have a habit of tangenting off of interesting points.

    If you want to keep all comments on topic, I’ll remember that for the future. Thanks for the response to my comment.

    [It’s not that it wasn’t on topic; it’s that you already knew the answer, or should have. -Raymond]
  11. Anonymous says:

    (As your response to my comment changed between my first response to the response and this one, please ignore my earlier response. Or not. Your blog, your choice.)

    I did think about it; I originally debated listing the possibilities I thought might happen (the function would work, but if G altered the parameter the results would be lost; an error would happen at run-time; an error would happen at compile-time), along with an explanation of why I didn’t have an obvious choice of actions (namely, I have experience with C, C++, and VB, but am still learning C#, and further, in NONE of those languages have I ever tried something quite as… odd as incrementing an operator in the middle of a function call in the middle of a calculation), but I chose to err on the side of a more concise, more readable query.

  12. Anonymous says:

    Raymond has an interesting post today about two subtle aspects of C#: how order of evaluation in an expression

  13. Eric Lippert says:

    Karellen:  

    Again, let me clarify that there are two principles here. First, that locals are always used after they are declared, and second, that the same identifier used two places in one block has the same meaning.

    I think we have established why the second principle is sensible. You are asking why the first principle is sensible.

    As Raymond pointed out, in JScript we implemented that declarations are logically hoisted to the top of the current scope, because JScript’s fundamental design principle is "muddle on through". If the programmer does something odd, we interpret it as best we can and keep going.

    In C#, we do NOT cleave to this design principle at all.  The reason is to prevent misunderstandings by choosing rules that enable the developer to comprehend their program more easily. What is the compelling benefit of allowing a use before a declaration? It’s potentially confusing and has no obvious benefit, so we don’t allow it.

  14. Eric Lippert says:

    And to answer your other question, I would like very much if C# had an annotated specification. It would make my job easier.

    However, the best you’re going to get is the blogs of people like me, Raymond, Eric Gunnerson, Wes Dyer, Mads Torgersen, Sreekar Choudhary, Charlie Calvert, etc. who discuss these design issues.

  15. Anonymous says:

    My own guess is that defining a strict order of evaluation makes short circuit operators possible.

    Here’s a VB.Net example:

    <code>If j <> 0 AndAlso i / j > 5 Then…</code>

    Written as such, you shouldn’t worry about a division by zero error.

  16. Anonymous says:

    Short circuiting is how the logical operators work in C, C++, and C# already.

    I guess logical operators are one of the few places where C and C++ were doing sequencing. Raymond used the comma operator as an example. I’m assuming it is one of several examples.

    Because in the section "if (f(x) && g(x)) { //stuff }" g(x) will never execute if f(x) winds up returning a false value.

    Since short circuiting is one of the "features" of the C and C++ languages, and C++ has a CLR implementation, they couldn’t realistically extend the language or not do short circuiting. So short circuiting is the way logical operators work.

    But in VB.Classic, the logical operators didn’t short. So they introduced the "AndAlso" and "OrElse" operators to not change the expectations of VB.Classic programmers moving to VB.Net

  17. Anonymous says:

    BA > Yes, && and || define sequence points in C and C++.

  18. Anonymous says:

    Karellen: It makes the use of stack space consistent. If a local variable’s lifetime didn’t extend to the start of a function it’s possible that a stack requirement might vary based on the execution path taken.

  19. Anonymous says:

    Karellen:

    The fact that the scope of a local variable extends to the beginning of the block it’s in even though you can’t reference it is useful in the situation where the local variable name hides another variable in an enclosing scope.

    If you look at Raymond’s example:

    int x;

    void g()

    {

     x = 3; // error – using variable before declared

     int x = 2;

    }

    }

    Without the ‘enclosing scope’ rule, the "x = 3" expression would be valid and would access the global x.  The designers of C# wanted to prevent that because it was something that sometimes caused bugs, and the use of that type of ‘delayed’ name hiding was not a tremendous benefit.

  20. Anonymous says:

    I think the question is more, "why doesn’t C explicitly specify the order of evaluation?".  The C standard has a number of things that aren’t defined, because doing so makes the language more flexible.  When you’re writing a compiler, you can choose the path that’s most optimised for the architecture.  For example, there might conceivably be some processor architecture out there where it is more efficient to have f() * g() evaluate g() first (although I can’t think why this would be).

    With C#, you’re always running on the CLR anyway, so you’re always compiling down to the same "architecture".  It’s not like C where you can generate different assembly for different architectures – you’re always generating IL.  So there’s no longer any reason for any ambiguity.  Indeed, ambiguity leads to problems, so it’s better to specify it.

  21. Anonymous says:

    Interesting stuff :)

    Always love these kinds of blog posts.

    I was struggling with this unintentionally a few weeks back (bad naming – was good it did this), so it seems very poetic to me that you touched on it.

  22. Anonymous says:

    mikeb > But according to Raymond’s post (and as brought up in my original query) you can’t have one variable shadow another anyway.

    Even if you could use x before it were declared, your particular program still wouldn’t compile as you’d get an "inner x shadows outer x" (or whatever the message is) error.

  23. Anonymous says:

    This reminds me of C++/Java difference in scope of for-loop variables (I’m not sure about C#, I’ve never used it).

    In C++, if you write:

    for (int i = 0; i < x; i++)

     foo(i);

    It gets compiled as if you wrote this:

    int i = 0;

    while (i < x) {

     foo(i);

     i++;

    }

    Which means that you can’t do this:

    for (int i = 0; i < x1; i++)

     foo1(i);

    for (int i = 0; i < x2; i++)

     foo2(i);

    Because there will be a compile error since the second loop redeclares i in the same scope.  In Java, however, the i only has the scope of the loop, so that last block of code would compile just fine.  I would guess the C# language designers made the same improvement, as it also avoids some subtle errors by forcing you to declare the variable outside of the loop if you actually intend to use it that way.

  24. Anonymous says:

    As someone who maintains other people’s C/C++ code, I welcome these changes. Maybe we won’t get any more of this:

    {

       HRESULT hr;

       (…)

       if (bla) {

           hr = DoThis(…);

       } else {

           // block copied from elsewhere…

           HRESULT hr = DoThat(…);

       }

       return hr;

    }

  25. Anonymous says:

    Karellen –

    The "no shadowing" rule only applies to locals. In Raymond’s example, the "x" that would be shadowed is a class member, and it’s legal (although frowned upon) to shadow class members with locals. That exact situation is why the scoping rules say that locals scope from the beginning of the block, so that anywhere within a block, a variable name will be unambiguous.

  26. Anonymous says:

    @Kip:

    which C++ compiler and what settings are you using?

  27. Anonymous says:

    Kip, that’s an unfortunate bug in some older C++ compilers (and in Microsoft’s current one, by default, due to backwards compatibility constraints.) The standard itself specifies Java-like rules for for-loop variable scoping.

  28. Anonymous says:

    @Daniel COlascione:

    If by current Microsoft compiler you mean the one included in VS 2005, then "Force conformance in for loop scope" is set to "Yes" by default.

  29. Anonymous says:

    Sorry I didn’t track down the source, I know I first learned about the behavior on Visual Studio 6.

    At work the C++ code I’m writing has to compile with VS 2005 on Windows, and it also has to compile for AIX and Solaris.  I’m honestly not sure what the compiler settings are (we have build tools to handle all that for us–all I have to do is type "mkmk" on a console).  I’d guess they enabled that setting to maintain back-compatibility when we moved from VS6 to VS.net then to VS 2005, or to maintain cross-compatibility with the Unix compilers.

  30. Anonymous says:

    Uh – hang on. If shadowing variables is now allowed and i cannot shadow i, how come x can shadow x? The outer x is declared in the block defining “class A”, so should overlap the x in A::g.

    Shouldn’t it? Or have I misread something?

    Second, if the scope of a variable covers the whole block, not just from the point of declaration, why can’t you do:

    class A {

     void f() {

       x = 3;

       int x;

     }

    }

    Doesn’t the fact that that doesn’t work mean that the variable’s scope *does* begin only at the point of declaration? If not, what does saying that a variable’s scope begins at the start of the block mean? How is it different from it’s scope starting at the point of declaration?

    *confused*

    [Don’t be lazy. I’m not going to do your homework for you. Read the C# language specification. Section 5.1.7 (“Local Variables”) will be useful. -Raymond]
  31. Anonymous says:

    Wolf > Ah! Now it makes sense!

    Sorry, I assumed that the shadowing rules might be in some way consistent or logical. My bad.

  32. Anonymous says:

    Karellen –

    The shadowing rules are indeed consistent and logical, although they may not appear so unless you recognise the breadth of the CLR.

    You can shadow class members with locals because they represent different categories of storage. The name "x" bound to a local is different from the name "x" bound to a member (which is in turn different from "x" bound to a static member). Therefore the compiler can distinguish between them, allowing shadowing to occur.

    Two different locals named "x" would require some kind of context-binding, though, which doesn’t exist in C#. Therefore it’s forbidden to introduce a new local variable with the same name as an existing one — the compiler wouldn’t have a good way to distinguish between them, and providing such a mechanism would lead to enabling a questionable (if not downright felonious) programming practice.

    Incidentally, this is related to the rules for member shadowing in derived classes. Check the spec in section 3.7.1 (http://msdn2.microsoft.com/en-us/library/aa691133(VS.71).aspx).

  33. Anonymous says:

    OK, I’ve read the relevant section of the spec.

    So, the spec says: “[The] lifetime extends from entry into the [block] with which it is associated, until execution of that [block] ends in any way.”

    Also: “Within the scope of a local variable, it is a compile-time error to refer to that local variable in a textual position that precedes its local-variable-declarator.”

    I still don’t get what the point is of saying that the lifetime of the variable extends from the start of the block, if the variable can’t be referenced before its textual declaration. I don’t see the difference between that, and just saying that the lifetime of the variable is from its declaration until the end of the enclosing block.

    Like Lauren, I’d have thought that if the lifetime of the variable truly covered the whole block, then all declarations should act “as-if” they were placed at the very start of the block, before any statements, no matter where they actually were in the source.

    With Eric’s test code, again, if the “x”‘s lifetime was in fact the whole block, I don’t see why moving the declaration to the top of the block would actually change the meaning of the program.

    Why must local variables be declared before they’re used if their lifetime extends to before the point where they’re actually declared?

    Or, why can’t variables be used at any point in their lifetime?

    Does this make any sense? Coming from a C and C++ background, do I have some completely off-base notion of “lifetime”?

    *still confused*

    Heh. Alternatively, does the C# spec have a corresponding rationale document? :)

    [That lifetime != visibility should not be that much of a shock to a C or C++ programmer:
    void F()
    {
     int i;
     { int i; }
    }
    

    The lifetime of the outer “i” is the entire function F, even though it is not visible in the inner block. As I noted in the article, the rationale is to remove certain classes of programming errors. -Raymond

    ]

  34. Anonymous says:

    Kip, as others have described, this was a bug in VC++ 6.  In VS2003 (and maybe 2002, never used it) you could enable certain warnings to make it behave like this.  In VS2005, you can set an option to make it behave like this, and the default for new projects is to make it behave like this.  I believe that converted projects use the old behavior, but I’m not 100% sure about that.

  35. Anonymous says:

    "You are asking why the [principle that locals are always used after they are declared] is sensible."

    Close, I’m asking why that principle is sensible *if* a variable’s lifetime extends to before the point where it is declared.

    I don’t get how the part of the spec that says that a variable’s lifetime extends back to the start of the block has any actual meaning. I don’t see how it makes the language any different from having the lifetime of the variable start at it’s declaration.

    The only meaning I /can/ extract from variable lifetime extending back to the start of the block is that the variable should be usable before its declaration.

    Personally, I think that you should only be able to use a variable after it’s been declared. That makes sense. That works how I’d expect things to work. But why extend the lifetime of variables to before their declaration? That’s what I don’t get.

    Sorry for taking a while to explain things.

  36. Anonymous says:

    >> However, the best you’re going to get is the blogs of people like me, Raymond, Eric Gunnerson, Wes Dyer, Mads Torgersen, Sreekar Choudhary, Charlie Calvert, etc. who discuss these design issues.  <<

    Uh-oh, Eric.  You may have just undone months of effort that Raymond has made to ensure that his blog is *not* seen as a reference.  It’s just a collection of entertaining and educational stories.

    [Eric merely wrote that I tell interesting stories. I can accept that. He didn’t write, “The official Microsoft position on C# design issues can be found on the blogs of people like…” -Raymond]
  37. Anonymous says:

    "When you’re writing a compiler, you can choose the path that’s most optimised for the architecture."

    I think it’s more along the lines of "when you’re writing a compiler, you can choose the implementation that’s *easiest*".

    If you’re writing a compiler that does things recursively, it might be easiest to just have it do the "rightmost"/"leafmost" things first. If it’s more iterative, it might be easiest to do the "leftmost"/"rootmost" things first.

  38. Dean Harding says:

    Igor: If C++ were such a perfect language, why do we also have Java, Visual Basic, Perl, Haskell, Python, Ruby, and every other language that was developer after C++? (Not to mention updates to languages developed prior to C++: C’89, COBOL ’85, Fortran ’90, Smalltalk, etc)

  39. Anonymous says:

    For example, there might conceivably be some

    processor architecture out there where it is

    more efficient to have f() * g() evaluate

    g() first (although I can’t think why this

    would be).

    You could imagine a compiler that adds parallel processing for you…  f() could be executed on one core/cpu and g() on another.

    I’m sure "for things we haven’t thought of" is the major reason for leaving things unspecified.

  40. Anonymous says:

    In particular, i is incremented before the function G is invoked although obviously the previous value of i is passed as its parameter. (I didn’t say original because I can’t tell whether F could have changed the value of i).

    As for the VC6++ "bug", ISO changed the `for’ scoping rules; VC6++ simply uses the obsolete rules.

  41. Anonymous says:

    itsadok: Yes, I was a bit confused.

    When I learn i++/i– in school, I was told being a suffix means "do it later" (Although I’m wrong in remembering "how late it will happen"), so when I saw "i++" happens before G(), I thought something is wrong there.

  42. Anonymous says:

    There is one annoying thing about c#, it seems to follow the old non-standard vc way mentioned a couple of time above already, a variable declared inside for/foreach has the scope of the surrounding block too. For example this won’t compile in c#:

    for(int i = 0; ; ) {}

    int i = 0; // error

  43. Anonymous says:

    Err… why exactly did we need C# again?

    Was the programming in C++ *that* hard, or was the C++ itself *that* bad?

    Someone please enlighten me.

  44. Anonymous says:

    The compiler has permission to do whatever it wants, as long as it can prove that the alternate has the same effect as the original one. Precedence of function arguments are not an exception from this rule.

  45. Anonymous says:

    > It makes the use of stack space consistent. If a local variable’s lifetime didn’t extend to the start of a function it’s possible that a stack requirement might vary based on the execution path taken.

    The compiler doesn’t have any constraints on when to allocate locals.

    [The .locals pseudo-opcode and LocalVariables property might disagree with you. -Raymond]
  46. Anonymous says:

    that’s an unfortunate bug in some older C++ compilers (and in Microsoft’s current one, by default, due to backwards compatibility constraints.) The standard itself specifies Java-like rules for for-loop variable scoping.

    No, the c++ standard at one point defined this behaviour, but the standard has changed recently.

  47. Anonymous says:

    [quote]

    The expression F(i) + G(i++) * H(i) is evaluated as if it were written like this:

    temp1 = F(i);

    temp2 = i++;

    temp3 = G(temp2);

    temp4 = H(i);

    return temp1 + temp3 * temp4;

    [/quote]

    Err… do you mean “F(i) + G(++i) * H(i)”?

    I thought i++ is evaluated after execution of all other elements in the statement.

    [“Hey, instead of doing even the slightest bit of research, I’ll just ask Raymond.” Don’t be lazy. Look it up. -Raymond]
  48. Anonymous says:

    Ok, I checked the linked post. So it should be:

    [quote]

    The expression F(i) + G(i++) * H(i) is evaluated as if it were written like this:

    temp1 = F(i);

    temp3 = G(i);

    temp4 = H(i);

    i++;

    return temp1 + temp3 * temp4;

    [/quote]

    instead because "then method G is called with the old value of i".

  49. Anonymous says:

    I’m still making it wrong… repost:

    [quote]

    The expression F(i) + G(i++) * H(i) is evaluated as if it were written like this:

    temp1 = F(i);

    temp2 = G(i);

    i++;

    temp4 = H(i);  // new i

    return temp1 + temp2 * temp4;

    [/quote]

  50. Anonymous says:

    gabest:

    yes, that is because of what Raymond said earlier; any local variable declared ANYWHERE, is considered to be declared at the top of it’s scope.

    the reasoning, again, as already stated, is to avoid bugs.

  51. Anonymous says:

    Cheong: look at Raymond’s code again, he does:

    temp2 = i++;

    temp3 = G(temp2);

    Note that it’s G(temp2) and not G(i). temp2 holds the value of i *before* it was incremented. Therefore it’s equivalent to G(i++) and not G(++i).

    Raymond: you have to cut people some slack…

  52. Anonymous says:

    That’s the trouble with this kind of thing, you cannot shadow
    variables anymore, so don’t worry about it, it’ll always be ok. Except
    when you can shadow variables (ie members) and you’ll get the same bugs
    but without really knowing to watch for them.

    I think this kind of ‘fix’ is reducing the skill or at least the
    knowledge required to be a programmer, just like in the previous blog
    entry having electronic cash registers are reducing the ability to add
    up in today’s youth! Was the ability to shadow a local variable that
    big a deal in old C++?

    “A maintenance programmer decides that she wants”

    what’s with this ‘she’ business from MS employees? Its not correct
    english (where the masculine form encompasses the female, but not
    vice-versa) and just sounds wrong. I’d say its political correctness,
    but every blog seems to be written like this.

    [There are cases where one might legitimately wish
    to shadow a member (constructors, mostly). But nobody could come up
    with cases where one would want to legitimately shadow a local
    variable. -Raymond
    ]
  53. austegard says:

    Cheong’s confusion (an mine before I ran it in SnippetCompiler) stems from the fact that the expanded version is missing a temp variable – it really ought to be:

    temp1 = F(i);

    temp2 = i++;

    temp3 = i;

    temp4 = G(temp2);

    temp5 = H(temp3);  //same as H(i), but more explicitly illustrates the change from temp2 to temp3

    return temp1 + temp4 * temp5;

  54. BryanK says:

    Karellen: Lifetime and scope can be different.  The lifetime of the variable (the length of time that storage is allocated for it) is the entire block in which it was defined, but its scope (the lines of code that are permitted to access it) is only from the point of declaration to the end of the block.

    You see the same thing happening with a static variable in a function in C/C++/C#: the lifetime is the same as the lifetime of the containing class (or in C, the compilation unit), but the scope is only the function.  Would you want to be able to access a function’s static variables from another function in the same class, just because their lifetime hasn’t ended?  I don’t.  :-)

    Here, the lifetime is the entire block (and in VB, the lifetime is actually the entire function for variables declared inside sub-function blocks, too: I’m not sure if C# has the same setup), but the scope just starts later.

    Now, that doesn’t completely explain the error, though, because it’s not really a lifetime issue (that only affects runtime).  It’s really just a scope issue.  It happens because the scope doesn’t start until the declaration.  I suspect the spec probably says something like "the compiler searches outward block-by-block when binding a variable, starting with the block that the variable was used in; it uses the first declaration it finds; if that declaration is out of scope, it’s an error" (but I don’t know that for sure, as I haven’t read the spec).  So I’m guessing it won’t bind the variable reference to the field unless *no* local variable has the name "x".

    (And that still doesn’t provide a rationale, but the various comments here should.  It helps with accidental bugs.)

    As for why the lifetime should start at the start of a block (or in VB, a function), that’s when their storage is (probably?) first allocated.  But lifetime doesn’t really have anything to do with this error message, either.

  55. Anonymous says:

    mikey: ah, that way it makes sense now :)

  56. Anonymous says:

    Since I don’t have Raymond’s email address to ask him otherwise, I’d prefer if he didn’t make it appear that I conflate blog postings with published articles.  But, given that this is a blog, it’s not like it matters, since one couldn’t use his blog posting as a reference to show anything anyways.

    [The “published articles” are just blog postings in dead tree form. So it’s all the same. -Raymond]
  57. Anonymous says:

    For-loop-scoped variables in C++ don’t have Java-like scope.  For-loop-scoped variables in Java have C++-like scope.  (In other words, Java is mostly cribbed from early-’90s C++, before everything that made C++ particularly interesting and useful was added.  Java omitted the sole particularly interesting pre-’90s feature, destructors. No, virtual functions aren’t interesting.)

    The example presented works fine with a correct (i.e. non-MS) compiler.  The local-variable scope rules have not "changed recently", and are not about to change.  Lots of other stuff is about to change, but without breaking existing programs.  C# has cribbed some of the less interesting bits, such as "auto".

  58. Anonymous says:

    people who are actively discussing that G(i++)increments i after evaluation fail to recognize the same fact for temp2 = i++. that should tell us something about the dangers of postfix increments…

  59. Anonymous says:

    ‘[The “published articles” are just blog postings in dead tree form. So it’s all the same. -Raymond]’

    So, TechNet Magazine is a yellow journalism rag?  And no one
    should quote anything from it as a reliable source because one never
    knows when an article is “just blog postings in dead tree form”?
     Well, I’m glad you’ve cleared that up.  I’ll be sure to
    avoid TechNet Magazine in the future.

    [I’m impressed at your inability to use other cues to assess the reliability of printed information, such as tone,
    choice of subject matter, and distinguishing between a reporter and a
    columnist. Reading a newspaper must be very confusing for you. -Raymond
    ]
  60. Anonymous says:

    Doh! One of those section numbers should be 3.11.5.1

  61. Anonymous says:

    BryanK: As for why the lifetime should start at the start of a block (or in VB, a function), that’s when their storage is (probably?) first allocated.

    I think one of the things which makes the C# rule confusing for C++ programmers is that C++ has a notion of "lifetime" that is different to storage. In C++, the object’s lifetime begins when its constructor completes. So saying the lifetime starts at the start of the block implies the constructor is run then. I’m guessing this doesn’t matter for C# because it doesn’t have user-defined constructors for stack objects.

    struct Demo { Demo() { cout << "Demon"; } };

    void func() {

       cout << "Startn";

       Demo d;

    }

    If the lifetime of d is moved to the start of func() the program’s output will change. In general, constructors would be a lot less useful.

  62. Anonymous says:

    AndyC: If a local variable’s lifetime didn’t extend to the start of a function it’s possible that a stack requirement might vary based on the execution path taken.

    And that would be bad because…?

    Stack space already depends on the execution path.

    void func() {

       if (rand()) {

            char a[100];

            stuff();

       }

       else {

            char a[200];

            stuff();

       }

    }

    Or imagine that each branch of the “if” came from inlining a different function.

    [Compile that in your favorite C++ compiler and look at the stack usage the compiler uses. I think you’ll be surprised. -Raymond]
  63. Anonymous says:

    ‘[I’m impressed at your inability to use other cues to assess the reliability of printed information, such as tone, choice of subject matter, and distinguishing between a reporter and a columnist. Reading a newspaper must be very confusing for you. -Raymond]’

    Now, I’m confused.  I just went through the effort of agreeing with you, that your articles in TechNet Magazine are unreliable (“just blog postings in dead tree form”), sensationalized (you well admit:  your blogs contain speculation, not checked facts), and are unprofessional (unless you want to start claiming that your blog is professional).  So, what sort of reputable magazine would allow unreliable, sensationalized, unprofessional columns?  Last I checked, those qualities fall under yellow journalism.  Perhaps TechNet Magazine is really the computer-world version of MAD Magazine?  Otherwise, I’m well out of ideas to explain your column.

    [I guess the New York Times is also yellow journalism. They have columnists too. If you feel so strongly that an informal storytelling column besmirches the standards of a magazine, then feel free to let the editors know. (PS, there is a middle ground between “professional” and “unprofessional.” It’s called “informal.”) -Raymond]
  64. Anonymous says:

    "No, the c++ standard at one point defined this behaviour, but the standard has changed recently."

    No, the c++ standard did not change. There may have been a change between successive *drafts* of the standard, but the point of drafts is that they’re not the finished article, and are subject to change.

    The c++ _standard_ has always mandated the current behaviour, ever since 1998 when it was ratified. Given that a change like this would not have been made between the final draft and the finished standard, it would have been introduced a while before then.

    According to my copy of "The Design and Evolution of C++" the use of declarations in condition statements "if (int i = foo()) { … }" was introduced in 1991, and the behaviour of for() statements was later changed to match it. (sections 3.11.5.2, 3.11.5.2).

    Given this, I think we can safely say that the "for()" behaviour is at least 10 years old, and has had a similar counterpart in "if()" statements for over 15 years. Let’s call it 1995.

    That makes the change older than Javascript, Python 2 and ASP, and as old as Windows ’95, Java, HTML 2, Ruby and PHP 2

    That’s some strange definition of "recent" you’ve got.

  65. Xepol says:

    Why?  Because you could do some wickedly stupid code in C.

    Take for example:

    i = 0;

    printf("%d %d %d",i,++i++,i+=49);

    What would you expect to print out?

    would you expect: 51 50 49

    Start tossing function calls in that alter other parameter values used and EVERYTHING starts get a little wierd and hard to debug.

  66. Anonymous says:

    @Raymond:  Consider, a food review column making distasteful remarks about a restaurant vs a food review column with a newspaper disclaimer of responsibility making distasteful remarks about a restaurant.  Consider, Ann Landers put on the front page versus Ann Landers put in the advice section.  Consider a website that reposts Ann Landers articles but doesn’t go through the bother of marking her columns as advice or, at least, disclaiming responsibility for their contents on the same page–okay, this isn’t a perfect analogy, given how well known Ann Landers is.

    I’m at a complete loss on how to get you to consider the situation objectively.  All I know is, I’m unwilling to endless argue against your red herrings.

    [I think you’ll find that very few online versions of print media clearly call out the nature of each article. It’s left for the reader to figure out. Here’s an example of an opinion column with no explicit indication that it’s an opinion column. Do the opinions expressed in that article therefore establish the position of the Seattle Times newspaper? -Raymond]
  67. Anonymous says:

    "That’s some strange definition of "recent" you’ve got."

    I’d give a little latitude on that, since Visual C++ 6.0 was released in 1998, and Visual C++ 5.0 was released in 1997 (at least according to my recollection and what I’m able to find on the web), so it is *possible* that it hadn’t yet been definitely extended to for() loops at the time the behavior of the compiler in those two versions was frozen.

  68. Anonymous says:

    [Compile that in your favorite C++ compiler and look at the stack usage the compiler uses. I think you’ll be surprised. -Raymond]

    The point remains. If the compiler choses to grow the stack once at the start instead of within each branch of the “if”, it could do the same for a variable declared in the middle of a function.

    Also, note what I said about the code resulting from functions being inlined, and then consider what happens if they aren’t inlined. Or think of a recursive function – it will use more stackspace if it recurses deeper. Clearly stack space usage depends on code paths.

    Finally, you’ve not answered the question: given the above, why would it be bad if the stack space used depended on code paths taken?

    [It would be bad because it would be a violation of MSIL. I challenge you to write a method in MSIL that uses a variable amount of stack space. -Raymond]

Comments are closed.