ART世界探险(7) - 数组

简介: Java中有专门处理数组相关的指令,并且有对越界检查等操作。这样,OAT编译出来的数组相关的代码,与本地C++写出的类似代码之间,就有了很大的不同。

ART世界探险(7) - 数组

Java针对数据是有专门的指令去处理的,这与C/C++有显著的不同。

Java字节码对于数组的支持

一个极简的例子

Java源代码

为了简化,我们取一个极简的例子来说明Java的数组指令的用法:

我们new一个长度为1的字节数组,然后返回这个数组的长度。

    public static int testByteArrayLength(){
        byte[] baArray = new byte[1];
        return baArray.length;
    }

Java字节码

有几条指令先交代一下:

  • newarray:这条指令用于创建一个新的数组,参数是类型。对于整型变量,byte,short和int占用的空间是一样的。但是变成数组之后就不一样了。byte数组可以节省空间。
  • arraylength:求数组长度是专门有一条指令来实现的。
  • aload:从变量中将数组引用load出来。一句话就是,要操作哪个数组,先把这个数组的引用从变量中读出来。
  • astore:将数组引用存入变量
  • baload:从byte型数组中读值
  • bastore:向byte型数组中写值

第0号是将数组长度1,从常量池1中读出来。
第1号,new一个长度在栈里,类型为byte的数组。
第3号,将生成好的这个数组的引用存到变量0中。
第4号,从变量0中,读取数组的引用。
第5号,从栈中数组的引用的数组中读取长度。
第6号,返回这个长度值。

  public static int testByteArrayLength();
    Code:
       0: iconst_1
       1: newarray       byte
       3: astore_0
       4: aload_0
       5: arraylength
       6: ireturn

Dalvik字节码

看了Dalvik字节码之后,不得不感叹,通过寄存器方式的指令,可读性确实提高了很多。

第1句:常量值1放到v1寄存器中
第2句:new一个类型为byte,长度在v1中的数组,引用放在v0里。
第3句:读取v0中所存引用的数组的长度,存到v1寄存器中。
第4句:返回v1的值。

  16: int com.yunos.xulun.testcppjni2.TestART.testByteArrayLength() (dex_method_idx=16793)
    DEX CODE:
      0x0000: 1211                         | const/4 v1, #+1
      0x0001: 2310 9308                    | new-array v0, v1, byte[] // type@2195
      0x0003: 2101                         | array-length v1, v0
      0x0004: 0f01                         | return v1

OAT生成的代码

我们终于要开始与Java强相关的指令正面交锋了。因为像new-array和array-length这样的指令是不会有对应的机器指令来对应的,因为要面对的层次有点高,不是物理机器这一层所关注的。这正是JVM与其他的真实或虚拟的机器非常不同的一点。
幸好,OAT的实现中,也将这样的功能封装到了各个过程中,比如new-array,对应到pAllocArray过程中。

我们来分析一下生成的OAT代码:

    CODE: (code_offset=0x005030bc size_offset=0x005030b8 size=100)...
      0x005030bc: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x005030c0: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x005030c4: f81e0fe0    str x0, [sp, #-32]!
      0x005030c8: f9000ffe    str lr, [sp, #24]
      0x005030cc: 79400250    ldrh w16, [tr](state_and_flags)
      0x005030d0: 35000230    cbnz w16, #+0x44 (addr 0x503114)

前面还是存参数,判断是否要被调试器suspend这些。
将长度参数1,先存到栈里,sp+16,再转到w1,给pAllocArray做为参数。另外一个参数是类型,就是byte code里看到的type@2195.

      0x005030d4: 52800030    mov w16, #0x1
      0x005030d8: b90013f0    str w16, [sp, #16]
      0x005030dc: b94013e1    ldr w1, [sp, #16]
      0x005030e0: f94003e2    ldr x2, [sp]
      0x005030e4: 52811260    mov w0, #0x893
      0x005030e8: f940ca5e    ldr lr, [tr, #400](pAllocArray)
      0x005030ec: d63f03c0    blr lr
      suspend point dex PC: 0x0001

返回值放sp+12,再读回来,这个是数组的引用。
数组的结构的下一个+8的位置存的就是数组的长度,这个就不麻烦再写条指令的专门实现了。

      0x005030f0: b9000fe0    str w0, [sp, #12]
      0x005030f4: b9400fe0    ldr w0, [sp, #12]
      0x005030f8: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0003
      GC map objects:  v0 ([sp + #12])
      0x005030fc: b9400801    ldr w1, [x0, #8]
      suspend point dex PC: 0x0003
      GC map objects:  v0 ([sp + #12])

长度暂存在sp+16,再读出来,放到w0里,最后返回。

      0x00503100: b90013e1    str w1, [sp, #16]
      0x00503104: b94013e0    ldr w0, [sp, #16]
      0x00503108: f9400ffe    ldr lr, [sp, #24]
      0x0050310c: 910083ff    add sp, sp, #0x20 (32)
      0x00503110: d65f03c0    ret
      0x00503114: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x00503118: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x0050311c: 17ffffee    b #-0x48 (addr 0x5030d4)

更实际一点的例子

Java源代码

我们写一段稍有点意义的数组访问的代码。极简的例子帮助我们学会了最简单的数组相关的指令,但是并没有读写数组的值。这个例子加入对数组的读写:

    public static int testArray(){
        int sum = 0;
        int[] iaTest = new int[10];
        iaTest[0]=0;
        iaTest[1]=1;
        for(int i=2;i<iaTest.length;i++){
            iaTest[i]=iaTest[i-1] + iaTest[i-2];
        }
        for(int i=0;i<iaTest.length;i++){
            sum += iaTest[i];
        }
        return sum;
    }

Java字节码

newarray, aload, astore和arraylength前面都学过了,这里面增加了从整型数组中读值的iaload和往整型数组里写的iastore。

  public static int testArray();
    Code:
       0: iconst_0
       1: istore_0
       2: bipush        10
       4: newarray       int
       6: astore_1
       7: aload_1
       8: iconst_0
       9: iconst_0
      10: iastore
      11: aload_1
      12: iconst_1
      13: iconst_1
      14: iastore
      15: iconst_2
      16: istore_2
      17: iload_2
      18: aload_1
      19: arraylength
      20: if_icmpge     43
      23: aload_1
      24: iload_2
      25: aload_1
      26: iload_2
      27: iconst_1
      28: isub
      29: iaload
      30: aload_1
      31: iload_2
      32: iconst_2
      33: isub
      34: iaload
      35: iadd
      36: iastore
      37: iinc          2, 1
      40: goto          17
      43: iconst_0
      44: istore_2
      45: iload_2
      46: aload_1
      47: arraylength
      48: if_icmpge     63
      51: iload_0
      52: aload_1
      53: iload_2
      54: iaload
      55: iadd
      56: istore_0
      57: iinc          2, 1
      60: goto          45
      63: iload_0
      64: ireturn

对应的Dalvik代码

Dalvik代码中用不到aload和astore,反正引用都在寄存器里。它通过aput和aget指令来读写数组。

  15: int com.yunos.xulun.testcppjni2.TestART.testArray() (dex_method_idx=16792)
    DEX CODE:
      0x0000: 1215                         | const/4 v5, #+1
      0x0001: 1204                         | const/4 v4, #+0
      0x0002: 1202                         | const/4 v2, #+0
      0x0003: 1303 0a00                    | const/16 v3, #+10
      0x0005: 2331 9708                    | new-array v1, v3, int[] // type@2199
      0x0007: 4b04 0104                    | aput v4, v1, v4
      0x0009: 4b05 0105                    | aput v5, v1, v5
      0x000b: 1220                         | const/4 v0, #+2
      0x000c: 2113                         | array-length v3, v1
      0x000d: 3530 1000                    | if-ge v0, v3, +16
      0x000f: d803 00ff                    | add-int/lit8 v3, v0, #-1
      0x0011: 4403 0103                    | aget v3, v1, v3
      0x0013: d804 00fe                    | add-int/lit8 v4, v0, #-2
      0x0015: 4404 0104                    | aget v4, v1, v4
      0x0017: b043                         | add-int/2addr v3, v4
      0x0018: 4b03 0100                    | aput v3, v1, v0
      0x001a: d800 0001                    | add-int/lit8 v0, v0, #+1
      0x001c: 28f0                         | goto -16
      0x001d: 1200                         | const/4 v0, #+0
      0x001e: 2113                         | array-length v3, v1
      0x001f: 3530 0800                    | if-ge v0, v3, +8
      0x0021: 4403 0100                    | aget v3, v1, v0
      0x0023: b032                         | add-int/2addr v2, v3
      0x0024: d800 0001                    | add-int/lit8 v0, v0, #+1
      0x0026: 28f8                         | goto -8
      0x0027: 0f02                         | return v2

OAT编译的代码

这个代码稍有点长。不过我们现在已经有充分的知识可以看懂了。
我来把每句Dalvik跟OAT代码对应起来,需要的地方再加两句讲解。

    CODE: (code_offset=0x00502d9c size_offset=0x00502d98 size=764)...
      0x00502d9c: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x00502da0: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x00502da4: f81c0fe0    str x0, [sp, #-64]!
      0x00502da8: f9001ffe    str lr, [sp, #56]
      0x00502dac: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502db0: 350014b0    cbnz w16, #+0x294 (addr 0x503044)

第2个常量1,存在sp+48里。

      // const/4 v5, #+1

      0x00502db4: 52800030    mov w16, #0x1
      0x00502db8: b90033f0    str w16, [sp, #48]

第1个常量0,存在sp+44里。这两个常量一会儿赋值的时候会用到。

      // const/4 v4, #+0

      0x00502dbc: 52800010    mov w16, #0x0
      0x00502dc0: b9002ff0    str w16, [sp, #44]

sum初值的那个0常量,放在sp+36里。

      // const/4 v2, #+0

      0x00502dc4: 52800010    mov w16, #0x0
      0x00502dc8: b90027f0    str w16, [sp, #36]

数组的长度10,存在sp+40里。

      // const/16 v3, #+10
      
      0x00502dcc: 52800150    mov w16, #0xa
      0x00502dd0: b9002bf0    str w16, [sp, #40]

把放存进去的长度10再读出来,类型type@2199传给w0,调pAllocArray去分配数组空间。

      // new-array v1, v3, int[] // type@2199
      
      0x00502dd4: b9402be1    ldr w1, [sp, #40]
      0x00502dd8: f94003e2    ldr x2, [sp]
      0x00502ddc: 528112e0    mov w0, #0x897
      0x00502de0: f940ca5e    ldr lr, [tr, #400](pAllocArray)
      0x00502de4: d63f03c0    blr lr
      suspend point dex PC: 0x0005

返回的数组引用在w0中,先暂存到sp+32。这时[x0]指向的就是数组了。
下一步做

iaTest[0]=0;

下面开始给数组的0下标位置赋0.

      // aput v4, v1, v4

      0x00502de8: b90023e0    str w0, [sp, #32]
      0x00502dec: b94023e0    ldr w0, [sp, #32]
      0x00502df0: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0007
      GC map objects:  v1 ([sp + #32])

将数组引用存到sp+24中,先加载回来。
然后,读数组的长度,数组的第8个字节开始的4个字节。
长度放到sp+20中。

      0x00502df4: b9001be0    str w0, [sp, #24]
      0x00502df8: b9401be0    ldr w0, [sp, #24]
      0x00502dfc: b9400801    ldr w1, [x0, #8]
      0x00502e00: b90017e1    str w1, [sp, #20]

sp+44的值读回来,往上翻翻,还记得吗,这就是那个0.
然后,体现Java的优越性的地方又出来了,它会做越界检查。如果越界了,就跳到后面去调用pThrowArrayBounds去抛越界异常。

      0x00502e04: b9402fe0    ldr w0, [sp, #44]
      0x00502e08: b94017e1    ldr w1, [sp, #20]
      0x00502e0c: 6b01001f    cmp w0, w1
      0x00502e10: 54001202    b.hs #+0x240 (addr 0x503050)

w0的值,就是sp+44里面的那个0,暂存到sp+16里。
sp+24读到w0中,往前找找吧,这个是数组的引用。
w1读取sp+16,刚存的那个1。
w2是sp+44,还是0。
add语句是根据下标计算应该存到数组的什么位置里。
w2的值1,存到add算出来的坐标再加上数组的头12字节(比如数组长度就在头里)里面。

      0x00502e14: b90013e0    str w0, [sp, #16]
      0x00502e18: b9401be0    ldr w0, [sp, #24]
      0x00502e1c: b94013e1    ldr w1, [sp, #16]
      0x00502e20: b9402fe2    ldr w2, [sp, #44]
      0x00502e24: 0b010810    add w16, w0, w1, lsl #2
      0x00502e28: b9000e02    str w2, [x16, #12]
      0x00502e2c: b94023e0    ldr w0, [sp, #32]
      0x00502e30: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0009
      GC map objects:  v1 ([sp + #32])

sp+24还是数组的引用。
w1存数组长度,sp+48是那个常量1.
然后是判数组越界等等,跟上面跟下标0赋0一样,这个是将数组下标1的值赋1:

iaTest[1]=1;

跟上面一样,大家应该比较熟悉了。

      // aput v5, v1, v5

      0x00502e34: b9001be0    str w0, [sp, #24]
      0x00502e38: b9401be0    ldr w0, [sp, #24]
      0x00502e3c: b9400801    ldr w1, [x0, #8]
      0x00502e40: b90017e1    str w1, [sp, #20]
      0x00502e44: b94033e0    ldr w0, [sp, #48]
      0x00502e48: b94017e1    ldr w1, [sp, #20]
      0x00502e4c: 6b01001f    cmp w0, w1
      0x00502e50: 54001042    b.hs #+0x208 (addr 0x503058)
      0x00502e54: b90013e0    str w0, [sp, #16]
      0x00502e58: b9401be0    ldr w0, [sp, #24]
      0x00502e5c: b94013e1    ldr w1, [sp, #16]
      0x00502e60: b94033e2    ldr w2, [sp, #48]
      0x00502e64: 0b010810    add w16, w0, w1, lsl #2
      0x00502e68: b9000e02    str w2, [x16, #12]

后面开始第一个for循环:

        for(int i=2;i<iaTest.length;i++){
            iaTest[i]=iaTest[i-1] + iaTest[i-2];
        }

下面是将常量值2,存到sp+28中。
sp+32,也就是v1,存的是数组引用。

      // const/4 v0, #+2
      
      0x00502e6c: 52800050    mov w16, #0x2
      0x00502e70: b9001ff0    str w16, [sp, #28]
      0x00502e74: b94023e0    ldr w0, [sp, #32]
      0x00502e78: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x000c
      GC map objects:  v1 ([sp + #32])

[x0+8]这是标准的array-length指令的翻译。

      // array-length v3, v1
      
      0x00502e7c: b9400801    ldr w1, [x0, #8]
      suspend point dex PC: 0x000c
      GC map objects:  v1 ([sp + #32])

数组长度这个值,存到sp+40中。
sp+28是循环控制变量。
然后二者做比较,如果大于等于就跳转到0x502f9c,这个循环结束。

      // if-ge v0, v3, +16
      
      0x00502e80: b9002be1    str w1, [sp, #40]
      0x00502e84: b9401fe0    ldr w0, [sp, #28]
      0x00502e88: b9402be1    ldr w1, [sp, #40]
      0x00502e8c: 6b01001f    cmp w0, w1
      0x00502e90: 1a9fb7e2    cset w2, ge
      0x00502e94: 2a0203e0    mov w0, w2
      0x00502e98: 35000820    cbnz w0, #+0x104 (addr 0x502f9c)
      
      // add-int/lit8 v3, v0, #-1
      
      0x00502e9c: b9401fe0    ldr w0, [sp, #28]
      0x00502ea0: 51000401    sub w1, w0, #0x1 (1)
      0x00502ea4: b9002be1    str w1, [sp, #40]
      0x00502ea8: b94023e0    ldr w0, [sp, #32]
      0x00502eac: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0011
      GC map objects:  v1 ([sp + #32])

      // aget v3, v1, v3
      
      0x00502eb0: b9001be0    str w0, [sp, #24]
      0x00502eb4: b9401be0    ldr w0, [sp, #24]
      0x00502eb8: b9400801    ldr w1, [x0, #8]
      0x00502ebc: b90017e1    str w1, [sp, #20]
      0x00502ec0: b9402be0    ldr w0, [sp, #40]
      0x00502ec4: b94017e1    ldr w1, [sp, #20]
      0x00502ec8: 6b01001f    cmp w0, w1
      0x00502ecc: 54000ca2    b.hs #+0x194 (addr 0x503060)
      0x00502ed0: b90013e0    str w0, [sp, #16]
      0x00502ed4: b9401be0    ldr w0, [sp, #24]
      0x00502ed8: b94013e1    ldr w1, [sp, #16]
      0x00502edc: 0b010810    add w16, w0, w1, lsl #2
      0x00502ee0: b9400e02    ldr w2, [x16, #12]
      0x00502ee4: b9002be2    str w2, [sp, #40]
      
      // add-int/lit8 v4, v0, #-2
      
      0x00502ee8: b9401fe0    ldr w0, [sp, #28]
      0x00502eec: 51000801    sub w1, w0, #0x2 (2)
      0x00502ef0: b9002fe1    str w1, [sp, #44]
      0x00502ef4: b94023e0    ldr w0, [sp, #32]
      0x00502ef8: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0015
      GC map objects:  v1 ([sp + #32])
      
      // aget v4, v1, v4
      
      0x00502efc: b9001be0    str w0, [sp, #24]
      0x00502f00: b9401be0    ldr w0, [sp, #24]
      0x00502f04: b9400801    ldr w1, [x0, #8]
      0x00502f08: b90017e1    str w1, [sp, #20]
      0x00502f0c: b9402fe0    ldr w0, [sp, #44]
      0x00502f10: b94017e1    ldr w1, [sp, #20]
      0x00502f14: 6b01001f    cmp w0, w1
      0x00502f18: 54000a82    b.hs #+0x150 (addr 0x503068)
      0x00502f1c: b90013e0    str w0, [sp, #16]
      0x00502f20: b9401be0    ldr w0, [sp, #24]
      0x00502f24: b94013e1    ldr w1, [sp, #16]
      0x00502f28: 0b010810    add w16, w0, w1, lsl #2
      0x00502f2c: b9400e02    ldr w2, [x16, #12]
      0x00502f30: b9002fe2    str w2, [sp, #44]
      
      // add-int/2addr v3, v4
      
      0x00502f34: b9402be0    ldr w0, [sp, #40]
      0x00502f38: b9402fe1    ldr w1, [sp, #44]
      0x00502f3c: 0b010002    add w2, w0, w1
      0x00502f40: b9002be2    str w2, [sp, #40]
      0x00502f44: b94023e0    ldr w0, [sp, #32]
      0x00502f48: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0018
      GC map objects:  v1 ([sp + #32])
      
      // aput v3, v1, v0
      
      0x00502f4c: b9001be0    str w0, [sp, #24]
      0x00502f50: b9401be0    ldr w0, [sp, #24]
      0x00502f54: b9400801    ldr w1, [x0, #8]
      0x00502f58: b90017e1    str w1, [sp, #20]
      0x00502f5c: b9401fe0    ldr w0, [sp, #28]
      0x00502f60: b94017e1    ldr w1, [sp, #20]
      0x00502f64: 6b01001f    cmp w0, w1
      0x00502f68: 54000842    b.hs #+0x108 (addr 0x503070)
      0x00502f6c: b90013e0    str w0, [sp, #16]
      0x00502f70: b9401be0    ldr w0, [sp, #24]
      0x00502f74: b94013e1    ldr w1, [sp, #16]
      0x00502f78: b9402be2    ldr w2, [sp, #40]
      0x00502f7c: 0b010810    add w16, w0, w1, lsl #2
      0x00502f80: b9000e02    str w2, [x16, #12]
      
      // add-int/lit8 v0, v0, #+1
      
      0x00502f84: b9401fe0    ldr w0, [sp, #28]
      0x00502f88: 11000401    add w1, w0, #0x1 (1)
      0x00502f8c: b9001fe1    str w1, [sp, #28]

      // goto -16
      
      0x00502f90: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502f94: 35000730    cbnz w16, #+0xe4 (addr 0x503078)
      0x00502f98: 17ffffb7    b #-0x124 (addr 0x502e74)

第一个循环结束,下面是第二个循环:

        for(int i=0;i<iaTest.length;i++){
            sum += iaTest[i];
        }

这个循环的计算量少了,就是纯遍历,应该更容易理解一些。
循环控制变量赋初值0:

      // const/4 v0, #+0

      0x00502f9c: 52800010    mov w16, #0x0
      0x00502fa0: b9001ff0    str w16, [sp, #28]
      0x00502fa4: b94023e0    ldr w0, [sp, #32]
      0x00502fa8: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x001e
      GC map objects:  v1 ([sp + #32])

循环结束的值,取数组长度。

      // array-length v3, v1

      0x00502fac: b9400801    ldr w1, [x0, #8]
      suspend point dex PC: 0x001e
      GC map objects:  v1 ([sp + #32])

如果sp+28(v0)的值大于等于sp+40(v3),循环就结束了,跳转到0x503034,return那一段。

      // if-ge v0, v3, +8
      
      0x00502fb0: b9002be1    str w1, [sp, #40]
      0x00502fb4: b9401fe0    ldr w0, [sp, #28]
      0x00502fb8: b9402be1    ldr w1, [sp, #40]
      0x00502fbc: 6b01001f    cmp w0, w1
      0x00502fc0: 1a9fb7e2    cset w2, ge
      0x00502fc4: 2a0203e0    mov w0, w2
      0x00502fc8: 35000360    cbnz w0, #+0x6c (addr 0x503034)
      0x00502fcc: b94023e0    ldr w0, [sp, #32]
      0x00502fd0: b940001f    ldr wzr, [x0]
      suspend point dex PC: 0x0021
      GC map objects:  v1 ([sp + #32])

如果循环可以继续,则将数组的值读出来。sp+28(v0)是当前索引值,sp+32(v1)是数组的引用,结果放到sp+40(v3)中。还是有数组越界的判断。

      // aget v3, v1, v0

      0x00502fd4: b9001be0    str w0, [sp, #24]
      0x00502fd8: b9401be0    ldr w0, [sp, #24]
      0x00502fdc: b9400801    ldr w1, [x0, #8]
      0x00502fe0: b90017e1    str w1, [sp, #20]
      0x00502fe4: b9401fe0    ldr w0, [sp, #28]
      0x00502fe8: b94017e1    ldr w1, [sp, #20]
      0x00502fec: 6b01001f    cmp w0, w1
      0x00502ff0: 540004a2    b.hs #+0x94 (addr 0x503084)
      0x00502ff4: b90013e0    str w0, [sp, #16]
      0x00502ff8: b9401be0    ldr w0, [sp, #24]
      0x00502ffc: b94013e1    ldr w1, [sp, #16]
      0x00503000: 0b010810    add w16, w0, w1, lsl #2
      0x00503004: b9400e02    ldr w2, [x16, #12]
      0x00503008: b9002be2    str w2, [sp, #40]

下面一条指令是算求和,v2=v2+v3。sp+36是v2,sp+40是v3.

      // add-int/2addr v2, v3
      
      0x0050300c: b94027e0    ldr w0, [sp, #36]
      0x00503010: b9402be1    ldr w1, [sp, #40]
      0x00503014: 0b010002    add w2, w0, w1
      0x00503018: b90027e2    str w2, [sp, #36]

循环控制变量sp+28(v0)加1

      // add-int/lit8 v0, v0, #+1

      0x0050301c: b9401fe0    ldr w0, [sp, #28]
      0x00503020: 11000401    add w1, w0, #0x1 (1)
      0x00503024: b9001fe1    str w1, [sp, #28]
      0x00503028: 79400250    ldrh w16, [tr](state_and_flags)
      0x0050302c: 35000310    cbnz w16, #+0x60 (addr 0x50308c)

      // goto -8

      0x00503030: 17ffffdd    b #-0x8c (addr 0x502fa4)

      // return v2

      0x00503034: b94027e0    ldr w0, [sp, #36]
      0x00503038: f9401ffe    ldr lr, [sp, #56]
      0x0050303c: 910103ff    add sp, sp, #0x40 (64)
      0x00503040: d65f03c0    ret

后面是一系列的判断suspend和抛数组越界异常的地方。

      0x00503044: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x00503048: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x0050304c: 17ffff5a    b #-0x298 (addr 0x502db4)
      0x00503050: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x00503054: d63f03c0    blr lr
      suspend point dex PC: 0x0007
      GC map objects:  v1 ([sp + #32])
      0x00503058: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x0050305c: d63f03c0    blr lr
      suspend point dex PC: 0x0009
      GC map objects:  v1 ([sp + #32])
      0x00503060: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x00503064: d63f03c0    blr lr
      suspend point dex PC: 0x0011
      GC map objects:  v1 ([sp + #32])
      0x00503068: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x0050306c: d63f03c0    blr lr
      suspend point dex PC: 0x0015
      GC map objects:  v1 ([sp + #32])
      0x00503070: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x00503074: d63f03c0    blr lr
      suspend point dex PC: 0x0018
      GC map objects:  v1 ([sp + #32])
      0x00503078: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x0050307c: d63f03c0    blr lr
      suspend point dex PC: 0x001c
      GC map objects:  v1 ([sp + #32])
      0x00503080: 17ffffc6    b #-0xe8 (addr 0x502f98)
      0x00503084: f942265e    ldr lr, [tr, #1096](pThrowArrayBounds)
      0x00503088: d63f03c0    blr lr
      suspend point dex PC: 0x0021
      GC map objects:  v1 ([sp + #32])
      0x0050308c: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x00503090: d63f03c0    blr lr
      suspend point dex PC: 0x0026
      GC map objects:  v1 ([sp + #32])
      0x00503094: 17ffffe7    b #-0x64 (addr 0x503030)

C++的数组实现

C++源代码

int testArray() {
    const int ARRAY_SIZE = 10;
    int sum = 0;
    int *iaTest = new int[ARRAY_SIZE];
    iaTest[0] = 0;
    iaTest[1] = 1;
    for (int i = 2; i < ARRAY_SIZE; i++) {
        iaTest[i] = iaTest[i - 1] + iaTest[i - 2];
    }
    for (int i = 0; i < ARRAY_SIZE; i++) {
        sum += iaTest[i];
    }
    return sum;
}

C++编译生成的代码-arm64 v8a

可以看到,这段代码,编译器已经将循环优化展开了。

0000000000000bf0 <_Z9testArrayv>:
 bf0:    a9bf7bfd     stp    x29, x30, [sp,#-16]!
 bf4:    d2800500     mov    x0, #0x28                      // #40
 bf8:    910003fd     mov    x29, sp
 bfc:    97ffff15     bl    850 <_Znam@plt>
 c00:    b900001f     str    wzr, [x0]
 c04:    52800022     mov    w2, #0x1                       // #1
 c08:    b9000402     str    w2, [x0,#4]
 c0c:    b9000802     str    w2, [x0,#8]
 c10:    52800042     mov    w2, #0x2                       // #2
 c14:    b9000c02     str    w2, [x0,#12]
 c18:    52800062     mov    w2, #0x3                       // #3
 c1c:    b9001002     str    w2, [x0,#16]
 c20:    528000a2     mov    w2, #0x5                       // #5
 c24:    b9001402     str    w2, [x0,#20]
 c28:    52800102     mov    w2, #0x8                       // #8
 c2c:    b9001802     str    w2, [x0,#24]
 c30:    528001a2     mov    w2, #0xd                       // #13
 c34:    b9001c02     str    w2, [x0,#28]
 c38:    528002a2     mov    w2, #0x15                      // #21
 c3c:    aa0003e1     mov    x1, x0
 c40:    b9002002     str    w2, [x0,#32]
 c44:    52800442     mov    w2, #0x22                      // #34
 c48:    b9002402     str    w2, [x0,#36]
 c4c:    3cc10420     ldr    q0, [x1],#16
 c50:    a8c17bfd     ldp    x29, x30, [sp],#16
 c54:    3dc00021     ldr    q1, [x1]
 c58:    4ea18400     add    v0.4s, v0.4s, v1.4s
 c5c:    4eb1b800     addv    s0, v0.4s
 c60:    0e043c00     mov    w0, v0.s[0]
 c64:    1100dc00     add    w0, w0, #0x37
 c68:    d65f03c0     ret

x86_64的C++编译生成代码

CISC指令的优势终于发挥出来了,arm64的指令都是32位的,4个字节。但是x86_64的指令可以是7个字节。
ARM只能通过str指令访问内存,x86可没有这种限制。

0000000000000dc0 <_Z9testArrayv>:
 dc0:    48 8d 64 24 f8           lea    -0x8(%rsp),%rsp
 dc5:    bf 28 00 00 00           mov    $0x28,%edi
 dca:    e8 11 fa ff ff           callq  7e0 <_Znam@plt>
 dcf:    c7 00 00 00 00 00        movl   $0x0,(%rax)
 dd5:    c7 40 04 01 00 00 00     movl   $0x1,0x4(%rax)
 ddc:    c7 40 08 01 00 00 00     movl   $0x1,0x8(%rax)
 de3:    c7 40 0c 02 00 00 00     movl   $0x2,0xc(%rax)
 dea:    c7 40 10 03 00 00 00     movl   $0x3,0x10(%rax)
 df1:    c7 40 14 05 00 00 00     movl   $0x5,0x14(%rax)
 df8:    c7 40 18 08 00 00 00     movl   $0x8,0x18(%rax)
 dff:    c7 40 1c 0d 00 00 00     movl   $0xd,0x1c(%rax)
 e06:    c7 40 20 15 00 00 00     movl   $0x15,0x20(%rax)
 e0d:    c7 40 24 22 00 00 00     movl   $0x22,0x24(%rax)
 e14:    b8 58 00 00 00           mov    $0x58,%eax
 e19:    48 8d 64 24 08           lea    0x8(%rsp),%rsp
 e1e:    c3                       retq   
 e1f:    90                       nop

mips64的C++生成代码

MIPS也是RISC,只能通过li,ld这样的load指令读内存,sw这样的指令写内存。所以也没有x86那样短。

0000000000000e50 <_Z9testArrayv>:
 e50:    67bdfff0     daddiu    sp,sp,-16
 e54:    ffbc0000     sd    gp,0(sp)
 e58:    3c1c0002     lui    gp,0x2
 e5c:    0399e02d     daddu    gp,gp,t9
 e60:    ffbf0008     sd    ra,8(sp)
 e64:    679c91b0     daddiu    gp,gp,-28240
 e68:    df998040     ld    t9,-32704(gp)
 e6c:    0320f809     jalr    t9
 e70:    24040028     li    a0,40
 e74:    dfbf0008     ld    ra,8(sp)
 e78:    ac400000     sw    zero,0(v0)
 e7c:    0040182d     move    v1,v0
 e80:    24040001     li    a0,1
 e84:    24020001     li    v0,1
 e88:    24050002     li    a1,2
 e8c:    24060003     li    a2,3
 e90:    24070005     li    a3,5
 e94:    24080008     li    a4,8
 e98:    2409000d     li    a5,13
 e9c:    240a0015     li    a6,21
 ea0:    240b0022     li    a7,34
 ea4:    ac620004     sw    v0,4(v1)
 ea8:    dfbc0000     ld    gp,0(sp)
 eac:    24020058     li    v0,88
 eb0:    ac640008     sw    a0,8(v1)
 eb4:    ac65000c     sw    a1,12(v1)
 eb8:    ac660010     sw    a2,16(v1)
 ebc:    ac670014     sw    a3,20(v1)
 ec0:    ac680018     sw    a4,24(v1)
 ec4:    ac69001c     sw    a5,28(v1)
 ec8:    ac6a0020     sw    a6,32(v1)
 ecc:    ac6b0024     sw    a7,36(v1)
 ed0:    03e00009     jr    ra
 ed4:    67bd0010     daddiu    sp,sp,16

结论

虽然优化得不如C++好,但是数组指令还是比较清晰的。Java自带越界检查,还是节省了不少编程的力气的。

目录
相关文章
【数组的使用续篇】
【数组的使用续篇】
45 0
|
算法 搜索推荐 程序员
爱上c++的第十五天:STL-常用算法
爱上c++的第十五天:STL-常用算法
102 0
爱上c++的第十五天:STL-常用算法
|
弹性计算 前端开发 IDE
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
|
vr&ar 图形学
【Unity3D 灵巧小知识点】 ☀️ | 快速查找场景中勾选Raycast Target的游戏对象
【Unity3D 灵巧小知识点】 ☀️ | 字符串截取,截取某个路径字符串中 末尾文件 的名字 47/100 发布文章 zhangay1998 未选择任何文件 Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。
【Unity3D 灵巧小知识点】 ☀️ | 快速查找场景中勾选Raycast Target的游戏对象
|
弹性计算 前端开发 IDE
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(一)
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(一)
|
SQL 前端开发 程序员
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(二)
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起……
High&NewTech:新物种?这是一种不需要写代码的程序猿,这事,得从Ta们掌握了 iVX工具(首个无代码编程语言)说起(二)
零元学Expression Design 4 - Chapter 5 教你如何用自制笔刷在5分钟内做出设计感效果
原文:零元学Expression Design 4 - Chapter 5 教你如何用自制笔刷在5分钟内做出设计感效果 本章将教你如何运用笔刷与简单线条,只要5分钟,就能做出设计感效果 ...
1201 0