通过字节码理解自增运算

文/年容

今天碰到一个题,然后尹小凡、覃小善、彭大年等三人挺感兴趣,遂立了个flag要搞清楚一下,然后也算有了今年的第一篇博文了。

源码如下,最后i、j、k的值为多少?

package io.github.ryan.simple;

public class Increment {
    public void inc() {
        int i = 1, j, k;
        i = i++;
        j = i++;
        k = i + ++i * i++;
    }
}

通过IntelliJ的ASM Bytecode Outline插件获取到inc()方法字节码操作顺序如下:

MAXSTACK  = 3    // 最大栈深为3
MAXLOCALS = 4    // 最大局部变量表长度为4
public inc()V
   L0
    LINENUMBER 5 // 执行后栈深为0,1#屉i为1
    ICONST_1     // 常量1入栈   
    ISTORE 1     // 常量1出栈并装入1#屉 
   L1
    LINENUMBER 6 // 执行后栈深为0,1#屉i为1
    ILOAD 1      // 1#屉的值1入栈      
    IINC 1 1     // 1#屉的值1自增为2    
    ISTORE 1     // 栈顶值1出栈并装入1#屉
   L2
    LINENUMBER 7 // 执行后栈深为0,1#屉i为1,2#屉j为1
    ILOAD 1      // 1#屉的值1入栈
    IINC 1 1     // 1#屉的值1自增为2
    ISTORE 2     // 栈顶值1出栈装入2#屉
   L3
    LINENUMBER 8 // 执行后栈深为0,局部变量表长为4
    ILOAD 1      // 1#屉值2入栈,栈深为1
    IINC 1 1     // 1#屉的值2自增为3
    ILOAD 1      // 1#屉值3入栈,栈深为2
    ILOAD 1      // 1#屉值3入栈,栈深为3
    IINC 1 1     // 1#屉值3自增为4
    IMUL         // 栈顶两个值相乘 3 * 3,并将结果入栈,栈深为2
    IADD         // 栈顶两个值相加 9 + 2,并将结果入栈,栈深为1
    ISTORE 3     // 栈顶值11出栈装入3#屉,栈深为0
   L4
    LINENUMBER 9 
    RETURN

几点说明:

  • 局部变量表可以看成是一排有编号的抽屉(从0开始编号),用于存放方法参数和局部变量。奇怪的是,看起来它的最大长度应该为3才对,因为inc()方法没有参数,并且只有i、j、k三个局部变量,可为什么最大长度会是4呢?因为对于非静态方法,0#屉存放的是方法所属对象的引用
  • L0、L1、L2等表示标签,L代表label
  • 多数指令是对操作栈的数进行操作的,IINC指令则比较特别,它直接对局部变量表上的数进行操作
  • i = 4,j = 1,k = 11

参考资料