13

私はしばらくの間 (Java) バイトコードに取り組んできましたが、なぜいくつかの命令が入力されたのかを尋ねることは一度もありませんでした。ADD 演算では、整数の加算と FP の加算を区別する必要があることは理解しています (これが、IADD と FADD がある理由です)。しかし、なぜ ISTORE と FSTORE を区別する必要があるのでしょうか? どちらも、スタックからローカル変数の位置に 32 ビットを移動するまったく同じ操作を伴いますか?

私が考えることができる唯一の答えは、これを防ぐための型安全性です:(ILOAD、ILOAD、FADD)。ただし、型安全性は Java 言語レベルですでに適用されていると思います。クラス ファイル形式は Java と直接結合されていません。これは、Java をサポートしていない言語に対して型安全性を強制する方法ですか? 何か考えはありますか?ありがとうございました。

編集: Reedyの回答をフォローアップします。私はこの最小限のプログラムを書きました:

public static void main(String args[])
{
    int x = 1;
}

コンパイルされたもの:

iconst_1
istore_1
return

バイトコード エディタを使用して、2 番目の命令を次のように変更しました。

iconst_1
fstore_1
return

java.lang.VerifyError: Expecting to find float on stack を返しました。

スタック上に型に関する情報がなく、ビットだけである場合、FSTORE 命令は float ではなく int を処理していることをどのように認識したのでしょうか?

注:この質問のより適切なタイトルが見つかりませんでした。お気軽に改善してください。

4

4 に答える 4

19

これらの命令は、プログラムがタイプセーフであることを保証するために入力されます。クラスをロードするとき、仮想マシンはバイトコードの検証を実行して、たとえば整数を期待するメソッドに float が引数として渡されていないことを確認します。この静的検証では、検証者が特定の実行パスのスタック上の値の型と数を判断できる必要があります。スタック フレーム内のローカル変数は型指定されていないため、ロードおよびストア命令には type タグが必要です (つまり、ローカル変数に istore を実行し、後で同じ位置に fstore を実行できます)。命令の型タグにより、検証者は各ローカル変数に格納されている値の型を知ることができます。

ベリファイアは、メソッド内の各オペコードを調べ、各オペコードを実行した後、スタックとローカル変数にどのような型があるかを追跡します。これは別の形式の型チェックであり、Java コンパイラによって行われるチェックの一部と重複していることは間違いありません。検証ステップは、VM が不正な命令を実行する原因となるコードのロードを防止し、各操作の前に型をチェックするという大きなランタイム ペナルティを被ることなく、Java プラットフォームの安全性を確保します。各オペコードのランタイム タイプ チェックは、メソッドが実行されるたびにパフォーマンス ヒットになりますが、静的検証はクラスがロードされるときに 1 回だけ実行されます。

ケース 1:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
istore_1                OK              []                     1: int
return                  OK              []                     1: int

ケース 2:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
fstore_1                Error: Expecting to find float on stack

エラーが発生するのは、fstore_1 がスタックに float を期待しているが、前の命令を実行した結果が int をスタックに残すことをベリファイアが認識しているためです。

この検証は、オペコードを実行せずに行われます。むしろ、Java コンパイラが(Integer)"abcd". "abcd"コンパイラは、 が文字列であり、 にキャストできないことを知るためにプログラムを実行する必要はありませんInteger

于 2010-04-14T14:23:56.180 に答える
4

Geoff Reedyは、クラスがロードされたときにベリファイアが何をするかを彼の回答で説明しました。JVMパラメータを使用してベリファイアを無効にできることを付け加えたいと思います。これはお勧めしません!

サンプルプログラム(iconstおよびfstoreを使用)の場合、検証を無効にして実行した結果、VMエラーが発生し、JVMが停止して次のメッセージが表示されます。

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing)
# Problematic frame:
# j  BytecodeMismatch.main([Ljava/lang/String;)V+0
#
...
于 2010-04-21T11:19:03.720 に答える
4

最初の質問に私の推測で答えると、これらのバイトコードは異なる実装を必要とする可能性があるため異なります。たとえば、特定のアーキテクチャでは、整数オペランドはメイン スタックに保持されますが、浮動小数点オペランドはハードウェア レジスタに保持される場合があります。

2 番目の質問に答えるために、クラスの実行時ではなく、クラスのロード時に VerifyError がスローされます。検証プロセスについては、こちらで説明しています。ノートパス #3。

于 2010-04-14T14:50:05.090 に答える
2

上記のように、すべてのバイトコードは静的データフロー分析でタイプセーフであることが証明されている必要があります。ただし、スタック上の値の型から型を推測できるため、これは_storeのような命令が異なる型を持っている理由を実際には説明していません。実際、pop、dup、swapなど、まさにそれを実行し、複数のタイプで動作する命令がいくつかあります。一部の命令が入力され、他の命令が入力されない理由は、Javaの元の開発者だけが説明できるものです。

于 2012-06-16T01:20:32.637 に答える