Thread: Operator Precedence?

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545

    Operator Precedence?

    I just noticed something a bit strange at the bottom of this page:
    http://www.cppreference.com/operator_precedence.html

    For this code:
    Code:
    float x = 1;
    x = x / ++x;
    It says:
    The value of x is not guaranteed to be consistent across different compilers, because it is not clear whether the computer should evaluate the left or the right side of the division first. Depending on which side is evaluated first, x could take a different value.
    But the ++ pre-increment operator is 2 levels above the / division operator, so I don't see why there would be any confusion about what to do first?
    Is that page incorrect? If not, how can it be true?

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Yes, but when should the compiler grab the value of x to use on the left side of the division: before anything happens, or just when it is needed? You don't know, and I don't know.

  3. #3
    Registered User
    Join Date
    Oct 2001
    Posts
    2,934
    >because it is not clear whether the computer should evaluate the left or the right side of the division first.
    So is the x on the left side of the division the original x, or the new x? The following suffers from the same ambiguity:
    Code:
    float x = 1;
    x = x / x++;

  4. #4
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cpjust View Post
    But the ++ pre-increment operator is 2 levels above the / division operator, so I don't see why there would be any confusion about what to do first?
    Is that page incorrect? If not, how can it be true?
    The relative precedence of the operators has nothing to do with it. Precedence only comes into play when there is an ambiguity about which operator should be applied first -- in other words, which expression the operator binds to most tightly. There is no ambiguity here in that regard. The preincrement operator clearly must bind to 'x' and nothing else.

    What is ambiguous is whether the left side or the right side of the division expression will be evaluated first.

  5. #5
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by brewbuck View Post
    The relative precedence of the operators has nothing to do with it. Precedence only comes into play when there is an ambiguity about which operator should be applied first -- in other words, which expression the operator binds to most tightly. There is no ambiguity here in that regard. The preincrement operator clearly must bind to 'x' and nothing else.

    What is ambiguous is whether the left side or the right side of the division expression will be evaluated first.
    I thought it would have done it in steps:
    First do ++x
    Then do x / x

    If I was writing a compiler, I'm sure that's how I'd make it work.

    And since ++ happens before /, I don't see the problem.
    x was 0, then it gets incremented to 1, so you have 1 / 1.
    The compiler can't see it as 0 / 0 because ++ has a higher precedence, and it would never see it as 0 / 1 would it?

  6. #6
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cpjust View Post
    I thought it would have done it in steps:
    First do ++x
    Then do x / x
    What's so special about the right hand side -- why should it get evaluated first?

    If I was writing a compiler, I'm sure that's how I'd make it work.
    The order of evaluation of subexpressions was deliberately left unspecified so that compilers could actually optimize stuff.

    And since ++ happens before /, I don't see the problem.
    Why do you think the ++ happens before / ? The order of evaluation is unspecified.

    The compiler can't see it as 0 / 0 because ++ has a higher precedence, and it would never see it as 0 / 1 would it?
    Again, precedence has absolutely nothing to do with this. If the precedence of / and ++ were reversed the exact same ambiguity would remain.

  7. #7
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by brewbuck View Post
    The order of evaluation of subexpressions was deliberately left unspecified so that compilers could actually optimize stuff.
    OK, maybe I'm not understanding the point of the Order of Evaluation correctly, but I thought it was the same as the order of operations that they teach us in math class.
    i.e. 2*4+5/2-6*9 = (2*4) + (5/2) - (6*9) instead of 2 * (4+5) / (2-6) * 9 or anything in between.

    Quote Originally Posted by brewbuck View Post
    Why do you think the ++ happens before / ? The order of evaluation is unspecified.
    A higher precedence means that operation will be evaluated before ones with a lower precedence, right? So how can it be unspecified if ++ is 2 levels higher than /

  8. #8
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    This has nothing to do with order of operations[*] and everything to do with the fact that you have the same letter in two different places. If it was y/++x, everything would be completely spiffy. But you have x/++x. Does the first x refer to the x we had when we started, or the x we get after incrementing? Yes, ++ happens before /, but that has nothing to do with the problem -- why should what appears on the right side of the division sign affect what appears on the left side of the division sign? The view of the standard is that there is no legitimate use of the expression "x/++x" and consequently the compiler can do whatever it wants.
    Last edited by tabstop; 05-02-2008 at 04:32 PM. Reason: see two posts down

  9. #9
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cpjust View Post
    OK, maybe I'm not understanding the point of the Order of Evaluation correctly, but I thought it was the same as the order of operations that they teach us in math class.
    i.e. 2*4+5/2-6*9 = (2*4) + (5/2) - (6*9) instead of 2 * (4+5) / (2-6) * 9 or anything in between.
    That's not the question. The question is, do you evaluate the (4+5) first, or the (6*9)? Clearly it doesn't matter, since the result does not depend on that.

    Maybe a very simple example will help.

    Code:
    foo() + bar();
    Will foo() get called first, or bar()? It is unspecified. There is no issue of precedence, since only a single operator is involved here.

    A higher precedence means that operation will be evaluated before ones with a lower precedence, right? So how can it be unspecified if ++ is 2 levels higher than /
    Precedence has nothing to do with order of evaluation. It has to do with how the operators bind to operands.

  10. #10
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    The compiler can't see it as 0 / 0 because ++ has a higher precedence, and it would never see it as 0 / 1 would it?
    Actually, I know a few compilers that will see it as "0/0" with optimizations turned on, but it's unusual for a compiler to try that. Anyway, it depends on how the compiler was designed.

    The compiler might see this source as:

    Code:
    ++x;
    x /= x;
    or

    Code:
    y = x;
    ++x;
    y /= x;
    Soma
    Last edited by phantomotap; 05-02-2008 at 03:59 PM.

  11. #11
    Just Lurking Dave_Sinkula's Avatar
    Join Date
    Oct 2002
    Posts
    5,005
    7. It is easier to write an incorrect program than understand a correct one.
    40. There are two ways to write error-free programs; only the third one works.*

  12. #12
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Perhaps this will explain it better. Take a look at the following line:
    Code:
    result = a() * b() + c() * d();
    Precedence rules tell us that the parse tree looks like this:
    Code:
             +
          /     \
        *         *
      /   \     /   \
    a()   b() c()   d()
    instead of for example this:
    Code:
        *
      /   \
    a()     +
          /   \
        b()    *
             /   \
           c()   d()
    However you don't know what order a(), b(), c() and d() will be called. That's evaluation, not precedence.
    It could call c() then d(), then do the second *, then call a, then b, then do the first *, then do the +. If the functions a, b, c, and d all make certain modifications to a global variable and then return the current value of that global variable for example, then different evaluation orders would produce different results.

    However, no compiler would be allowed to generate the second parse tree I showed as it has incorrect precedence rules applied to it. Only using brackets could allow such a parse tree to result.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  13. #13
    Registered User
    Join Date
    Oct 2001
    Posts
    2,934
    >If it was y/++x, everything would be completely spiffy.
    That would be undefined as well if there's an x left of the assignment operator.

  14. #14
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    This may or may not help, but I think I'll throw it in just for the sake of it
    Give a formula such as: x = y / z
    In math, it would be easy: just divide y with z.

    Now, let's try to try play a compiler
    Because the data needed for the instructions needs to be stored inside the processor registers, the compiler needs to read the data, and then store it in a register before performing the opcode.

    So, basically it should happen like:
    Store the value of variable y into register A.
    Store the value of variable z into register B.
    Perform opcode.

    But there's no telling in what order it might store the data, so it might just as well do:
    Store the value of variable z into register B.
    Store the value of variable y into register A.

    So, if we re-write the formula as z = x / ++x
    The compiler can do

    (Left to right)
    Store the value of x (left side) into register A.
    Store the value of x (right side) into register B and increment it.
    Perform opcode.

    But on the other hand, it might do:

    (Right to left)
    Store the value of variable x (right side) in register B and increment it.
    (At this point, the value of x is now original x + 1, which must be reflected inside the variable x. In debug mode, I assume the compiler will write back the new value to the variable x, but in release, I assume it might just try to move the contents of register B directly into register A.)
    Store the value of variable x (left side) [which is now original x + 1] into register A.
    Perform opcode.

    Which would invoke undefined behaviour. Certainly, the compiler can do other optimizations, too. If the values are of different variables, then technically it shouldn't matter since the value of those variables would not affect each other during the phase the compiler fetches them and puts them into registers. So no bad behaviour there.

    Anyway, I think that's a little of how I see it.
    (Although I don't know if it's 100% what happens, especially with the ++x.)
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  15. #15
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,666
    Given
    f() + g() * h();
    The only thing the operator precedence table tells you is that * happens before +, and that's all.
    In which order the 3 functions are called is totally up to the compiler (evaluating the operands).

    On a register-rich machine, you could imagine f() being called first, but on a register-poor machine, perhaps it's called last.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

Popular pages Recent additions subscribe to a feed