LoginSignup
1

More than 1 year has passed since last update.

JavaScriptで、for文の初期化部分においてletで宣言された変数は本当にループごとに異なるインスタンスになるのか

Last updated at Posted at 2021-12-23

前回

JavaScriptで、なぜfor文の初期化部分においてletで宣言された変数はループごとに異なるインスタンスを持ちうるのかの続き。

疑問

前回の調査で、規格上では「ループ各回で環境が作成される」ことがわかった。そうすると「結構コスト高な気が…」という気がしてくる。

実際、前回の例のようにクロージャがループ内で作成される場合は、ループ各回で作成されるクロージャ内でそれぞれの環境を参照しているため、ループ各回で環境の生成が必要となる。

const functions = [];
for (let i = 0; i < 3; i++) {
  functions.push(() => console.log(i));
}
functions[0]();
functions[1]();
functions[2]();

だが、以下のようなfor

for (let i = 0; i < 3; i++) {
    console.log(i);
}

で、クロージャ作成が無い場合は、ループ各回で作成した環境は他のどこからも参照されないので、ループ内の環境は一つでも動作は同じである。最適化してくれるのではないか?

調査

実際にNode.jsでどう処理されるか調べてみた。Node.jsはV8を実行エンジンとして採用している。最適化するかどうかに関してはV8での話限定になってしまうが、最適化されるか? という調査観点からすれば十分である。

V8

基本動作はFiring up the Ignition interpreterで述べられている。
通常JavaScriptエンジンはJITコンパイルしてスクリプトを実行するが、JITコンパイルされたコードはメモリをたくさん使ってしまう場合があるなどオーバーヘッドがある。多くはこのオーバーヘッドは無駄なので、V8ではスクリプトをバイトコード列にコンパイルし、そのバイトコードを実行するインタプリタ(Ignition)を作った。
(実行回数が多いなどパフォーマンス上影響が大きい部分は最適化したJITコンパイラ(Crankshaft/TurboFan)でコンパイルしたコードを実行する)

結局はIgnitionのバイトコードがスクリプト実行の元となるので、ここで生成されたバイトコードを見れば動きがわかるはずだ。

Ignition

Ignitionはレジスタマシンで、特別なレジスタであるaccumulatorを持った構造である。JVMなどはスタックマシンであるが、レジスタマシン/スタックマシンの違いによりバイトコードのサイズや実行速度などが変わってくる。レジスタマシンは、オペランドの指定が必要なためバイトコードサイズが大きくなるが、実行速度が速い傾向がある。Ignitionでは自動的に入出力指定されるaccumulatorを持たせることでオペランド指定を減らし、実行速度を維持しつつバイトコードサイズも減らそうとしたのだろう。
Ignition Design Doc

In order to reduce the size of the bytecode stream, Ignition has an accumulator register, which is used by many bytecodes as implicit input and output register.

ニーモニック

バイトコードに関する概要がUnderstanding V8's Bytecodeで述べられている。
よく出てくる略語は以下。

略語 実際の語 意味
Ld Load メモリや直値でレジスタに値を読み込む
St Store レジスタからメモリやレジスタに値を書き込む
a accumulator accumulatorを対象レジスタとして指定する
Smi Small integer 整数直値
r register レジスタ指定

これらの略語を勘案すると、なんとなく以下のような気がしてくる。

ニーモニック 意味
LdaZero accumulatorの値を0にする
LdaSmi 3 accumulatorの値を3にする
Star r2 accumulatorの値をr2に書き込む
LdaGlobal 0 Global環境で0で指定されるオブジェクトをaccumulatorに読み込む
LdaNamedProperty r2 1 r2が指すオブジェクトにおいて1で指定されるプロパティをaccumulatorに読み込む

ぱっと見でわかりそうなものもある。

ニーモニック 意味
TestLessThan r0 accumulatorの値がr0の値より小かどうか判定(し、結果をフラグなどに記録)
JumpIfFalse 28 フラグを見てfalseだったら28ワード先にジャンプする
Inc accumulatorの値に1を加算する

など。正確な動作はV8のソースとか見ればわかるだろうが、今回の目的はそこまでは必要ないので略する。

バイトコードを見る

読める気になったところで、実際にバイトコードを見てみる。

クロージャを作る場合

まずはループ各回で環境が作られる様子を確認したい。以下のjsファイルを作る。

let-for-closures.js
const letFunctions = [];

function letClosures() {
  for (let i = 0; i < 3; i++) { 
    letFunctions.push(() => console.log(i));
  }
}

letClosures();
letFunctions.forEach(f => f());

Node.jsでコンパイルする。--print-bytecodeオプションでバイトコードが出力される。これだけだと出力が膨大になるので、--print-bytecode-filterオプションを指定して出力範囲を見たい関数だけにする。

% node --print-bytecode --print-bytecode-filter=letClosures let-for-closures.js
[generated bytecode for function: letClosures (0x0a271c6a0e31 <SharedFunctionInfo letClosures>)]
Parameter count 1
Register count 8
Frame size 64
   45 E> 0xa271c6a1656 @    0 : a7                StackCheck 
   65 S> 0xa271c6a1657 @    1 : 0b                LdaZero 
         0xa271c6a1658 @    2 : 26 f8             Star r3
         0xa271c6a165a @    4 : 26 fb             Star r0
         0xa271c6a165c @    6 : 0c 01             LdaSmi [1]
         0xa271c6a165e @    8 : 26 fa             Star r1
  130 E> 0xa271c6a1660 @   10 : a7                StackCheck 
         0xa271c6a1661 @   11 : 82 00             CreateBlockContext [0]
         0xa271c6a1663 @   13 : 16 f7             PushContext r4
         0xa271c6a1665 @   15 : 0f                LdaTheHole 
         0xa271c6a1666 @   16 : 1d 04             StaCurrentContextSlot [4]
         0xa271c6a1668 @   18 : 25 fb             Ldar r0
         0xa271c6a166a @   20 : 1d 04             StaCurrentContextSlot [4]
         0xa271c6a166c @   22 : 0c 01             LdaSmi [1]
         0xa271c6a166e @   24 : 67 fa 00          TestEqual r1, [0]
         0xa271c6a1671 @   27 : 9a 07             JumpIfFalse [7] (0xa271c6a1678 @ 34)
         0xa271c6a1673 @   29 : 0b                LdaZero 
         0xa271c6a1674 @   30 : 26 fa             Star r1
         0xa271c6a1676 @   32 : 8b 08             Jump [8] (0xa271c6a167e @ 40)
   76 S> 0xa271c6a1678 @   34 : 1a 04             LdaCurrentContextSlot [4]
         0xa271c6a167a @   36 : 4c 01             Inc [1]
   76 E> 0xa271c6a167c @   38 : 1d 04             StaCurrentContextSlot [4]
         0xa271c6a167e @   40 : 0c 01             LdaSmi [1]
         0xa271c6a1680 @   42 : 26 f9             Star r2
   70 S> 0xa271c6a1682 @   44 : 1a 04             LdaCurrentContextSlot [4]
         0xa271c6a1684 @   46 : 26 f6             Star r5
         0xa271c6a1686 @   48 : 0c 03             LdaSmi [3]
   70 E> 0xa271c6a1688 @   50 : 69 f6 02          TestLessThan r5, [2]
         0xa271c6a168b @   53 : 9a 04             JumpIfFalse [4] (0xa271c6a168f @ 57)
         0xa271c6a168d @   55 : 8b 06             Jump [6] (0xa271c6a1693 @ 61)
         0xa271c6a168f @   57 : 17 f7             PopContext r4
         0xa271c6a1691 @   59 : 8b 3d             Jump [61] (0xa271c6a16ce @ 120)
         0xa271c6a1693 @   61 : 0c 01             LdaSmi [1]
         0xa271c6a1695 @   63 : 67 f9 03          TestEqual r2, [3]
         0xa271c6a1698 @   66 : 9a 26             JumpIfFalse [38] (0xa271c6a16be @ 104)
   52 E> 0xa271c6a169a @   68 : a7                StackCheck 
   87 S> 0xa271c6a169b @   69 : 19 f7 04 00       LdaImmutableContextSlot r4, [4], [0]
         0xa271c6a169f @   73 : ac 01             ThrowReferenceErrorIfHole [1]
         0xa271c6a16a1 @   75 : 26 f5             Star r6
  100 E> 0xa271c6a16a3 @   77 : 28 f5 02 04       LdaNamedProperty r6, [2], [4]
         0xa271c6a16a7 @   81 : 26 f6             Star r5
         0xa271c6a16a9 @   83 : 81 03 00 02       CreateClosure [3], [0], #2
         0xa271c6a16ad @   87 : 26 f4             Star r7
  100 E> 0xa271c6a16af @   89 : 59 f6 f5 f4 06    CallProperty1 r5, r6, r7, [6]
         0xa271c6a16b4 @   94 : 0b                LdaZero 
         0xa271c6a16b5 @   95 : 26 f9             Star r2
         0xa271c6a16b7 @   97 : 1a 04             LdaCurrentContextSlot [4]
         0xa271c6a16b9 @   99 : 26 fb             Star r0
         0xa271c6a16bb @  101 : 8a 28 01          JumpLoop [40], [1] (0xa271c6a1693 @ 61)
         0xa271c6a16be @  104 : 0c 01             LdaSmi [1]
  130 E> 0xa271c6a16c0 @  106 : 67 f9 08          TestEqual r2, [8]
         0xa271c6a16c3 @  109 : 9a 06             JumpIfFalse [6] (0xa271c6a16c9 @ 115)
         0xa271c6a16c5 @  111 : 17 f7             PopContext r4
         0xa271c6a16c7 @  113 : 8b 07             Jump [7] (0xa271c6a16ce @ 120)
         0xa271c6a16c9 @  115 : 17 f7             PopContext r4
         0xa271c6a16cb @  117 : 8a 6b 00          JumpLoop [107], [0] (0xa271c6a1660 @ 10)
         0xa271c6a16ce @  120 : 0d                LdaUndefined 
  132 S> 0xa271c6a16cf @  121 : ab                Return 
Constant pool (size = 4)
Handler Table (size = 0)
0
1
2

ひとつひとつ見ていく。

   65 S> 0xa271c6a1657 @    1 : 0b                LdaZero 
         0xa271c6a1658 @    2 : 26 f8             Star r3
         0xa271c6a165a @    4 : 26 fb             Star r0
         0xa271c6a165c @    6 : 0c 01             LdaSmi [1]
         0xa271c6a165e @    8 : 26 fa             Star r1

r0r3を0にし、r11にしている。この0はあとの変数iの初期値に使用する。

  130 E> 0x59e8e561660 @   10 : a7                StackCheck 
         0x59e8e561661 @   11 : 82 00             CreateBlockContext [0]
         0x59e8e561663 @   13 : 16 f7             PushContext r4
         0x59e8e561665 @   15 : 0f                LdaTheHole 
         0x59e8e561666 @   16 : 1d 04             StaCurrentContextSlot [4]
         0x59e8e561668 @   18 : 25 fb             Ldar r0
         0x59e8e56166a @   20 : 1d 04             StaCurrentContextSlot [4]
  • 0xa271c6a1661CreateBlockContextがあり、その後PushContext r4がある。これは環境を作って一つ中の環境に移っているということ。
  • その後、LdaTheHole以下で、環境内の変数(i)を無効値で初期化→r0の値(=0)で書き換えている。
    • (スクリプトのfor (let i = 0;…の値を1に書き換えると、r01になるので、1iの初期値として書き込まれる)
         0x59e8e56166c @   22 : 0c 01             LdaSmi [1]
         0x59e8e56166e @   24 : 67 fa 00          TestEqual r1, [0]
         0x59e8e561671 @   27 : 9a 07             JumpIfFalse [7] (0x59e8e561678 @ 34)
         0x59e8e561673 @   29 : 0b                LdaZero 
         0x59e8e561674 @   30 : 26 fa             Star r1
         0x59e8e561676 @   32 : 8b 08             Jump [8] (0x59e8e56167e @ 40)
   76 S> 0x59e8e561678 @   34 : 1a 04             LdaCurrentContextSlot [4]
         0x59e8e56167a @   36 : 4c 01             Inc [1]
   76 E> 0x59e8e56167c @   38 : 1d 04             StaCurrentContextSlot [4]
  • 最初はr1の値は1なので、JumpIfFalseではジャンプしない。r10にして0x59e8e56167eへジャンプ。このジャンプは、+1(Inc)する処理を初回は飛ばすように機能する。これにより、すでに初期値が入っているところに余計に+1されないようにしている。
  • 2回目以降に来たときはr10になっているので、変数i+1する処理が入る。
         0x59e8e56167e @   40 : 0c 01             LdaSmi [1]
         0x59e8e561680 @   42 : 26 f9             Star r2
   70 S> 0x59e8e561682 @   44 : 1a 04             LdaCurrentContextSlot [4]
         0x59e8e561684 @   46 : 26 f6             Star r5
         0x59e8e561686 @   48 : 0c 03             LdaSmi [3]
   70 E> 0x59e8e561688 @   50 : 69 f6 02          TestLessThan r5, [2]
         0x59e8e56168b @   53 : 9a 04             JumpIfFalse [4] (0x59e8e56168f @ 57)
         0x59e8e56168d @   55 : 8b 06             Jump [6] (0x59e8e561693 @ 61)
         0x59e8e56168f @   57 : 17 f7             PopContext r4
         0x59e8e561691 @   59 : 8b 3d             Jump [61] (0x59e8e5616ce @ 120)
  • r21にし、
  • 変数iの値をr5に入れる。
  • r53と比較して、(3 < r5が)falseなら0x2ec0ffaa168fにジャンプ。ジャンプするとPopContextして環境を一つ外に移して最後の方にジャンプしている。ということはループを抜ける処理である。つまり「変数i3より小でないならループを終わる 」なので、forのループ判定をここでやっていることがわかる。
         0x59e8e561693 @   61 : 0c 01             LdaSmi [1]
         0x59e8e561695 @   63 : 67 f9 03          TestEqual r2, [3]
         0x59e8e561698 @   66 : 9a 26             JumpIfFalse [38] (0x59e8e5616be @ 104)
   52 E> 0x59e8e56169a @   68 : a7                StackCheck 
   87 S> 0x59e8e56169b @   69 : 19 f7 04 00       LdaImmutableContextSlot r4, [4], [0]
         0x59e8e56169f @   73 : ac 01             ThrowReferenceErrorIfHole [1]
         0x59e8e5616a1 @   75 : 26 f5             Star r6
  100 E> 0x59e8e5616a3 @   77 : 28 f5 02 04       LdaNamedProperty r6, [2], [4]
         0x59e8e5616a7 @   81 : 26 f6             Star r5
         0x59e8e5616a9 @   83 : 81 03 00 02       CreateClosure [3], [0], #2
         0x59e8e5616ad @   87 : 26 f4             Star r7
  100 E> 0x59e8e5616af @   89 : 59 f6 f5 f4 06    CallProperty1 r5, r6, r7, [6]

  • r21でなかったら0x59e8e5616beにジャンプするが、いまは1なので進む。
  • ImmutableContextSlotからr6に値を格納。Immutableなので、変数letFunctionsである。letFunctionsの中のプロパティをr5に入れて、クロージャを作ってr5を呼び出している。r5は関数ということなので、これはletFunctions.push(() => console.log(i))におけるpush()の呼び出しである。作ったクロージャを配列に追加しているところ。
         0x59e8e5616b4 @   94 : 0b                LdaZero 
         0x59e8e5616b5 @   95 : 26 f9             Star r2
         0x59e8e5616b7 @   97 : 1a 04             LdaCurrentContextSlot [4]
         0x59e8e5616b9 @   99 : 26 fb             Star r0
         0x59e8e5616bb @  101 : 8a 28 01          JumpLoop [40], [1] (0x59e8e561693 @ 61)
  • r20にして、変数iの値をr0へ。その後0x59e8e561693へ戻る。
  • 0x59e8e561693ではr20なので、今回は0x59e8e5616beへ。
         0x59e8e5616be @  104 : 0c 01             LdaSmi [1]
  130 E> 0x59e8e5616c0 @  106 : 67 f9 08          TestEqual r2, [8]
         0x59e8e5616c3 @  109 : 9a 06             JumpIfFalse [6] (0x59e8e5616c9 @ 115)
         0x59e8e5616c5 @  111 : 17 f7             PopContext r4
         0x59e8e5616c7 @  113 : 8b 07             Jump [7] (0x59e8e5616ce @ 120)
         0x59e8e5616c9 @  115 : 17 f7             PopContext r4
         0x59e8e5616cb @  117 : 8a 6b 00          JumpLoop [107], [0] (0x59e8e561660 @ 10)
  • r20なのでまたジャンプ。0x59e8e5616c9へ。
  • 0x59e8e5616c9ではPopContextして環境を一つ外にし、その後ループの先頭の方、0x59e8e561660にジャンプ。
  • 0x59e8e561660では、新しく環境を作って、変数iの値をr0で初期化。r0はループ途中の値でいまは1なので、iの値が1として新しい環境が作られる。ループが進むたびに+1されていくので、前回のループ時の環境の図のようになっている。
         0x59e8e5616ce @  120 : 0d                LdaUndefined 
  132 S> 0x59e8e5616cf @  121 : ab                Return 
  • ループが終わるのはi >= 3のとき。0x59e8e56168fではPopContextして環境を一つ外にして0x59e8e5616ceに来る。
  • LdaUndefinedでは関数letClosures()の戻り値を設定している。この関数は値を返さないので、undefinedとなる。関数の戻り値はaccumulatorで受け渡しされるようだ。

ループ内でクロージャを作る場合は、ループ各回で環境が作成されていそうなことがわかった。つまり、変数iはループ各回で異なるインスタンスになっている。

クロージャを作らない場合

つぎはループ内でクロージャを作らない場合。以下のjsファイルを作る。

let-for-simple.js
function letFor() {
  for (let i = 0; i < 3; i++) { 
    console.log(i);
  }
}

letFor();

同様にバイトコードを出力する。

% node --print-bytecode --print-bytecode-filter=letFor let-for-simple.js
[generated bytecode for function: letFor (0x0f1943760df1 <SharedFunctionInfo letFor>)]
Parameter count 1
Register count 3
Frame size 24
   15 E> 0xf19437614be @    0 : a7                StackCheck 
   35 S> 0xf19437614bf @    1 : 0b                LdaZero 
         0xf19437614c0 @    2 : 26 fb             Star r0
   40 S> 0xf19437614c2 @    4 : 0c 03             LdaSmi [3]
   40 E> 0xf19437614c4 @    6 : 69 fb 00          TestLessThan r0, [0]
         0xf19437614c7 @    9 : 9a 1c             JumpIfFalse [28] (0xf19437614e3 @ 37)
   22 E> 0xf19437614c9 @   11 : a7                StackCheck 
   57 S> 0xf19437614ca @   12 : 13 00 01          LdaGlobal [0], [1]
         0xf19437614cd @   15 : 26 f9             Star r2
   65 E> 0xf19437614cf @   17 : 28 f9 01 03       LdaNamedProperty r2, [1], [3]
         0xf19437614d3 @   21 : 26 fa             Star r1
   65 E> 0xf19437614d5 @   23 : 59 fa f9 fb 05    CallProperty1 r1, r2, r0, [5]
   46 S> 0xf19437614da @   28 : 25 fb             Ldar r0
         0xf19437614dc @   30 : 4c 07             Inc [7]
         0xf19437614de @   32 : 26 fb             Star r0
         0xf19437614e0 @   34 : 8a 1e 00          JumpLoop [30], [0] (0xf19437614c2 @ 4)
         0xf19437614e3 @   37 : 0d                LdaUndefined 
   77 S> 0xf19437614e4 @   38 : ab                Return 
Constant pool (size = 2)
Handler Table (size = 0)
0
1
2

短い。

  • r00にし、3と比較。ここはループ判定とわかる。
  • 0xf19437614ca以下は、console.log(i)の呼び出しということは先ほど見てきたletFunctions.push()からすぐにわかる。
  • その後r0の値を+1して、ループの頭に戻っている。

すなわち、環境も作らず、PushContext/PopContextもない。変数iはレジスタr0を割り当てている。いちいち環境に格納するまでもない、ということである。このように最適化によって環境を作る処理が省略されている。

結論

クロージャがある場合はループの各回で環境が作られるが、必要ないところではちゃんと最適化されていることがわかった。

varだと?

クロージャを作る場合

[generated bytecode for function: varClosures (0x3b4588b60e31 <SharedFunctionInfo varClosures>)]
Parameter count 1
Register count 4
Frame size 32
         0x3b4588b61626 @    0 : 84 00 01          CreateFunctionContext [0], [1]
         0x3b4588b61629 @    3 : 16 fb             PushContext r0
   45 E> 0x3b4588b6162b @    5 : a7                StackCheck 
   65 S> 0x3b4588b6162c @    6 : 0b                LdaZero 
   65 E> 0x3b4588b6162d @    7 : 1d 04             StaCurrentContextSlot [4]
   70 S> 0x3b4588b6162f @    9 : 1a 04             LdaCurrentContextSlot [4]
         0x3b4588b61631 @   11 : 26 fa             Star r1
         0x3b4588b61633 @   13 : 0c 03             LdaSmi [3]
   70 E> 0x3b4588b61635 @   15 : 69 fa 00          TestLessThan r1, [0]
         0x3b4588b61638 @   18 : 9a 25             JumpIfFalse [37] (0x3b4588b6165d @ 55)
   52 E> 0x3b4588b6163a @   20 : a7                StackCheck 
   87 S> 0x3b4588b6163b @   21 : 19 fb 04 00       LdaImmutableContextSlot r0, [4], [0]
         0x3b4588b6163f @   25 : ac 01             ThrowReferenceErrorIfHole [1]
         0x3b4588b61641 @   27 : 26 f9             Star r2
  100 E> 0x3b4588b61643 @   29 : 28 f9 02 01       LdaNamedProperty r2, [2], [1]
         0x3b4588b61647 @   33 : 26 fa             Star r1
         0x3b4588b61649 @   35 : 81 03 00 02       CreateClosure [3], [0], #2
         0x3b4588b6164d @   39 : 26 f8             Star r3
  100 E> 0x3b4588b6164f @   41 : 59 fa f9 f8 03    CallProperty1 r1, r2, r3, [3]
   76 S> 0x3b4588b61654 @   46 : 1a 04             LdaCurrentContextSlot [4]
         0x3b4588b61656 @   48 : 4c 05             Inc [5]
   76 E> 0x3b4588b61658 @   50 : 1d 04             StaCurrentContextSlot [4]
         0x3b4588b6165a @   52 : 8a 2b 00          JumpLoop [43], [0] (0x3b4588b6162f @ 9)
         0x3b4588b6165d @   55 : 0d                LdaUndefined 
  132 S> 0x3b4588b6165e @   56 : ab                Return 
Constant pool (size = 4)
Handler Table (size = 0)
3
3
3
  • CreateFunctionContextが最初に実行され、環境はそれ以外作られない。varは関数内スコープとなるので、直感的な予想とも合致する。

クロージャを作らない場合

今回は関数内で他にvar/let/constで宣言された変数とかがないため、letと同じ結果が得られる。

[generated bytecode for function: varFor (0x0d1a8bba0df1 <SharedFunctionInfo varFor>)]
Parameter count 1
Register count 3
Frame size 24
   15 E> 0xd1a8bba14be @    0 : a7                StackCheck 
   35 S> 0xd1a8bba14bf @    1 : 0b                LdaZero 
         0xd1a8bba14c0 @    2 : 26 fb             Star r0
   40 S> 0xd1a8bba14c2 @    4 : 0c 03             LdaSmi [3]
   40 E> 0xd1a8bba14c4 @    6 : 69 fb 00          TestLessThan r0, [0]
         0xd1a8bba14c7 @    9 : 9a 1c             JumpIfFalse [28] (0xd1a8bba14e3 @ 37)
   22 E> 0xd1a8bba14c9 @   11 : a7                StackCheck 
   57 S> 0xd1a8bba14ca @   12 : 13 00 01          LdaGlobal [0], [1]
         0xd1a8bba14cd @   15 : 26 f9             Star r2
   65 E> 0xd1a8bba14cf @   17 : 28 f9 01 03       LdaNamedProperty r2, [1], [3]
         0xd1a8bba14d3 @   21 : 26 fa             Star r1
   65 E> 0xd1a8bba14d5 @   23 : 59 fa f9 fb 05    CallProperty1 r1, r2, r0, [5]
   46 S> 0xd1a8bba14da @   28 : 25 fb             Ldar r0
         0xd1a8bba14dc @   30 : 4c 07             Inc [7]
         0xd1a8bba14de @   32 : 26 fb             Star r0
         0xd1a8bba14e0 @   34 : 8a 1e 00          JumpLoop [30], [0] (0xd1a8bba14c2 @ 4)
         0xd1a8bba14e3 @   37 : 0d                LdaUndefined 
   77 S> 0xd1a8bba14e4 @   38 : ab                Return 
Constant pool (size = 2)
Handler Table (size = 0)
0
1
2

(おわり)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1