9

Assume such situation:

int a = (--t)*(t-2);
int b = (t/=a)+t;

In C and C++ this is undefined behaviour, as described here: Undefined behavior and sequence points

However, how does this situation look in:

  • JavaScript,
  • Java,
  • PHP...
  • C#
  • well, any other language which has compound operators?

I'm bugfixing a Javascript -> C++ port right now in which this got unnoticed in many places. I'd like to know how other languages generally handle this... Leaving the order undefined is somehow specific to C and C++, isn't it?

Community
  • 1
  • 1
Kos
  • 70,399
  • 25
  • 169
  • 233
  • 9
    Maybe it's best not to know it :) – Daniel Daranas Feb 14 '11 at 13:59
  • 2
    @Daniel: Thoroughly agree! But, unfortunately for the OP, it sounds as if he/she is having to debug legacy code with this kind of crud in it... – Oliver Charlesworth Feb 14 '11 at 14:01
  • I'd say it's best not to know it when you write code, but know it when you read it. :) – Kos Feb 14 '11 at 14:02
  • The second assignment is fine. There is no undefined behavior there. – Ferruccio Feb 14 '11 at 14:03
  • @Ferruccio, right :) I edited it not to confuse any more. – Kos Feb 14 '11 at 14:05
  • Java defines exactly what the effective order is of operations in any syntactically valid expression. I suspect (but don't know) all the others you list do the same or very similar. JITs etc. can re-order at low level only if they know it'll give the same result as the "correct" order - can't remember whether or not it must also throw the same exceptions, probably must. This affects optimization, most notably in the sense that you can have expressions that have undefined behavior in C but are faster than they would be in Java. This was a kind of performance not considered worth keeping :-) – Steve Jessop Feb 14 '11 at 14:10
  • Example regarding exceptions - `(a / (b[0] - b[0])) + b[1]` - must this throw for the division by 0 if `b` has size 1, or is it allowed to throw out of bounds? In C, the fact that if anything goes wrong it's undefined behaviour, means there's no motivation to define which should occur "first" out of two ops that for particular data could fail. So in C there's potentially a little bit more freedom to reorder for optimization than in Java, where someone somewhere might care which exception is thrown. And that's why C, C++, and nobody else, has such confusing rules on sequence points :-) – Steve Jessop Feb 14 '11 at 14:14
  • Use the same rule as when using parenthesis: whenever you have doubts on what the code does, refactor it. There is no guarantee that if YOU understand it, that the next guy after you will also understand it. Possibly, he might think otherwise and start changing perfectly working code. – Patrick Feb 14 '11 at 16:33
  • 1
    given this question seems to be about all languages that are not c++ or c, does it really make sense to tag it c++ and c? – jk. Feb 14 '11 at 16:42
  • 3
    http://stackoverflow.com/questions/3720458/sequence-point-concept-in-java is the answer for Java , http://stackoverflow.com/questions/4644328/behaviour-and-order-of-evaluation-in-c for C# – nos Feb 14 '11 at 17:16
  • @Steve Jessop: "can have expression [undefined] in C but...faster than...Java / not considered worth keeping". That's misrepresentative. It also and crucially allows more optimisations in well-defined code - a *real* benefit that's still not considered worth keeping by many other languages. But hell - if they're that much slower to begin with they won't notice a little extra sloth.... – Tony Delroy Feb 15 '11 at 08:50
  • @Tony: I've heard the theory, and believe that cases exist, but I can't for the moment construct one. I rarely look at a Java expression and think to myself, "gosh, if this was C that would optimize further". In cases which are well-defined in C, and also guaranteed not to throw in Java, normally both optimizers can go to town, because it doesn't matter what order execution actually happens in when all the caller can possibly see is the result. In cases where the Java expression might throw, loss of performance in Java is more likely due to extra checks performed than order of execution. – Steve Jessop Feb 15 '11 at 10:35

4 Answers4

7

According to the ECMA Script specification, which I believe javascript is supposed to conform to, in multiplication and addition statements, it evaluates the left hand side before evaluating the right hand side. (see 11.5 and 11.6). I think this means that the code should be equivalent to

t = t - 1;
int a = t * (t - 2);
t = t / a;
int b = t + t;

However, you should not always trust the specification to be the same as the implementation!

Your best bet in confusing cases like this is to experiment with various inputs to the ambiguous lines of code in the original operating environment, and try to determine what it is doing. Make sure to test cases that can confirm a hypothesis, and also test cases that can falsify it.

Edit: Apparently most JavaScript implements the 3rd edition of ECMAScript, so I changed the link to that specification instead.

Null Set
  • 5,374
  • 24
  • 37
  • 1
    @zzzzBov The code is the same, I just tried to justify the choices made and provide a strategy for debugging/porting the code. As it turns out, the order that the side effects of operators happen in is well defined in ECMAScript. – Null Set Feb 16 '11 at 01:10
  • touché. I suppose I never explicitly mentioned the order of ops for JavaScript. – zzzzBov Feb 16 '11 at 03:30
3

Practically speaking, if you have to ask or look up the rules for an expression, you shouldn't be using that expression in your code. Someone else will come back two years from now and get it wrong, then rewrite it and break the code.

If this was intended as a strictly theoretical question I unfortunately can't offer details regarding those other languages.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 1
    "you shouldn't be using that expression in your code" - I think the practical application of the question is, "how do I replace this expression originally designed for Javascript, with equivalent C++". The questioner knows that the code he has is bad, but unless he understands Javascript's expression evaluation rules he can't replace it with something equivalent (assuming the original author also wasn't lavish with comments, documentation, or a comprehensive set of test cases, any of which could also resolve any ambiguity). Then the purely theoretical part is "what about other languages". – Steve Jessop Feb 15 '11 at 10:39
2

For javascript the following article should help.

This article clearly states whether a particular combination of

a OP b OP c goes from left-to-right and in which order.

I'm don't know about the other languages.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1
    That link doesn't explicitly spell out if it's required to re-read a variable's value at each step in the expression though. – Mark B Feb 14 '11 at 16:09
  • @Mark B: It looks very similar to the C precedence table, and the order of side effects is undefined in C. – David Thornley Feb 14 '11 at 17:11
1

However, how does this situation look in: JS, Java, PHP, C#...

To be perfectly candid, int a = (--t)*(t-2); int b = (t/=a)+t; looks like crap.

It's nice to have fancy code that can be all pretty and elitist, but there's absolutely no need for it. The solution for every language when confronted with code like this is to add a couple more semi-colons (unless you're dealing with python):

--t;
int a = t * (t-2);
t /= a;
int b = t + t;
-or-
int b = t * 2;
-or-
int b = t << 1;
//whichever method you prefer

If a different order of operations is desired, the adjust the lines accordingly. If you're trying to fix old buggy code, fix the code, don't just re-implement someone else's spaghetti.

Edit to add:

I realized I never specifically answered the original question:

How do languages handle side effects of compound operators?

Poorly.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • 1
    This does not address the problem. Berating the original coder will not help Kos decipher the ambiguous behavior of the original javascript code and reproduce that behavior in c++ – Null Set Feb 14 '11 at 15:57
  • It does address the *actual* problem, which is that the code needs to work. To make the code work, you simply break it down into separate parts. As long as the perens were left intact, order of operations should be the same as what I've posted above. There's no reason to rely on compound operators *especially* when their behavior is undefined. – zzzzBov Feb 14 '11 at 16:02
  • -1 as it doesn't addres the question, which has nothing to do with good vs ugly code, but sequence points – nos Feb 14 '11 at 17:02
  • @zzzzBov: Except that there is pre-existing Javascript code that is, presumably, working, using these sorts of expressions. It is easy enough to break down such expressions as you say, but how do you know you're reproducing the right behavior? – David Thornley Feb 14 '11 at 17:10
  • 1
    @David Thornley- Run the existing code and your cleaned-up code side-by-side. If your refactoring altered the order of operations, you should get different results. That won't tell you if the original code was actually doing what it was intended to do, but it will at least tell you if you altered anything. – bta Feb 14 '11 at 18:34
  • A problem here is that the original code has undefined behavior in C and C++, so testing it doesn't have to return a valid result. If your fixed code has a different result, is that good or bad? – Bo Persson Feb 14 '11 at 19:56
  • @Bo Persson, The code was originally JS, and has a defined behavior in JS. Porting it to C(++) is a matter of matching the behavior, not the code. – zzzzBov Feb 14 '11 at 20:03