Difference between prefix and postfix ++ operators

2019-07-20 20:52发布

问题:

There are a few questions regarding this (like Java: Prefix/postfix of increment/decrement operators?) but I'm not asking about the general difference between postfix and prefix ++ operators (I know that part), but about the fundamental difference between them at the Java specification level.

Specifically, is there any difference between prefix and postfix ++ operators other than operator precedence (maybe in the way the javac translates the commands to bytecode or in the way the JVM runs that bytecode)?

For example, would the following code necessarily run the same (in every JVM):

for (int i = 0; i < X; i++) { ... }

And

for (int i = 0; i < X; ++i) { ... }

Is there anything in the JLS that defines that these 2 statements would run in exactly the same way on every platform, Java compiler, JVM, etc., or is it possible (even theoretically) that these 2 statements will run differently?

回答1:

The relevant parts of the specification are:

15.15.1. Prefix Increment Operator ++

[…]

At run time, if evaluation of the operand expression completes abruptly, then the prefix increment expression completes abruptly for the same reason and no incrementation occurs. Otherwise, the value 1 is added to the value of the variable and the sum is stored back into the variable. […]
The value of the prefix increment expression is the value of the variable after the new value is stored.

 

15.14.2. Postfix Increment Operator ++

[…]

At run time, if evaluation of the operand expression completes abruptly, then the postfix increment expression completes abruptly for the same reason and no incrementation occurs. Otherwise, the value 1 is added to the value of the variable and the sum is stored back into the variable. […]
The value of the postfix increment expression is the value of the variable before the new value is stored.

So, the only difference is the result value when used in an expression context. When using it in the context of a for loop’s update clause, there relevant part is:

14.14.1.2. Iteration of for Statement

[…]

→ First, if the ForUpdate part is present, the expressions are evaluated in sequence from left to right; their values, if any, are discarded. If evaluation of any expression completes abruptly for some reason, the for statement completes abruptly for the same reason; any ForUpdate statement expressions to the right of the one that completed abruptly are not evaluated.

Taking it literally there would be a difference in the code as these expressions produce different results which are then discarded. However, this difference lies in non-observable behavior and as a consequence, the code is usually compiled to not produce the value in the first place and hence does not differ.

So the answer is, the code may have differences, e.g. when compiled naively, however the observable behavior is guaranteed to be the same.



回答2:

Yes, it would run the same. It would compile to the same bytecode, thus JVM will notice no difference whatever JVM it is.

You may check it by yourself:

public class TestIncrement {
    public void testPost(int X) {
        for (int i = 0; i < X; i++) {
            System.out.println(i);
        }
    }

    public void testPre(int X) {
        for (int i = 0; i < X; ++i) {
            System.out.println(i);
        }
    }
}

Both methods are compiled in the same way using either JavaC compiler:

public void testPost(int);
Code:
   0: iconst_0
   1: istore_2
   2: iload_2
   3: iload_1
   4: if_icmpge     20
   7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  10: iload_2
  11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
  14: iinc          2, 1
  17: goto          2
  20: return

Or ECJ compiler:

public void testPost(int);
Code:
  0: iconst_0
  1: istore_2
  2: goto          15
  5: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
  8: iload_2
  9: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
 12: iinc          2, 1
 15: iload_2
 16: iload_1
 17: if_icmplt     5
 20: return

Different compilers generate different bytecode, but in both cases the bytecode is the same for both methods.



回答3:

One of the main points of Java is that the code will run the same on every JVM. While compilers can generate different bytecode for the same source code, they need to follow a set of rules, just like the different JVMs, to ensure that things run correctly.

There is no undefined or platform dependent behaviour like in for example C.



回答4:

From Oracle documentation:

The increment expression is invoked after each iteration through the loop; it is perfectly acceptable for this expression to increment or decrement a value.

From Oracle documentation on JLS:

First, if the ForUpdate part is present, the expressions are evaluated in sequence from left to right; their values, if any, are discarded.

As it is invoked after the iteration and the return values are discarded, it shouldn't be any difference between both options. Maybe the bytecode is not exactly the same, but it should be equivalent.



回答5:

There are already good answers here, but I would like to try to answer it from another perspective. I will only look at how the for loop must work, in any language that has the same type of for loop that Java does.

It has four parts:

  • An initialization part
  • A conditional part
  • A loop variable modification part
  • A body part to loop through

Any compiler needs to handle them separately, they can't optimize by joining them in any way. (According to me anyway.) Each part must be looked at as an isolated entity of some kind. We are only interested in the loop variable modification part in this discussion.

Since it's an isolated entity, it can never matter if you use i++ or --i. No Java compiler, any other compiler for other languages or any language parser can handle the for loop differently in that respect.

That means the answer must be: Yes, they must run in the same way.