web-dev-qa-db-ja.com

Javaでの未定義の動作

私は SOに関するこの質問 を読んでいましたが、C++での未定義の一般的な動作について説明していましたが、Javaにも未定義の動作がありますか?

その場合、Javaの未定義の動作の一般的な原因は何ですか?

そうでない場合、Javaのどの機能がこのような動作から解放されますか?また、CおよびC++の最新バージョンがこれらのプロパティで実装されていないのはなぜですか?

15
Eight

Javaでは、誤って同期されたプログラムの動作を未定義と見なすことができます。

Java 7 JLSは 17.4.8。実行と因果関係の要件

f|dを使用して、fのドメインをdに制限することによって与えられる関数を示します。 x内のすべてのdf|d(x) = f(x)、およびx内にないすべてのdの場合、f|d(x)未定義...

Java APIドキュメントでは、結果が未定義の場合の例をいくつか示しています-たとえば、(非推奨の)コンストラクター Date(int year、int month、int day)

結果はndefinedです。指定された引数が範囲外の場合...

ExecutorService.invokeAll(Collection) 状態のJavadoc:

このメソッドの結果はndefinedです。この操作の進行中に特定のコレクションが変更された場合...

あまり正式でない種類の「未定義」の動作は、たとえば ConcurrentModificationException にあり、APIドキュメントでは「ベストエフォート」という用語を使用しています。

フェイルファストの動作は保証されないことに注意してください。一般的に言えば、同期されていない同時変更が存在する場合にハードな保証を行うことは不可能です。フェイルファスト操作は、ベストエフォートベースでConcurrentModificationExceptionをスローします。したがって、この例外に正しさを依存するプログラムを書くのは間違っています...


付録

質問の1つ comments は、Eric Lippertによる記事を参照しており、トピックの問題への役立つ導入を提供しています:Implementation-defined behaviour

言語にとらわれない推論にはこの記事をお勧めしますが、作者がJavaではなくC#をターゲットにしていることを覚えておく価値はあります。

従来、プログラミング言語のイディオムは未定義の動作を持っていると言います。期待どおりに動作したり、ハードディスクを消去したり、マシンをクラッシュさせたりする可能性があります。さらに、コンパイラの作成者は、未定義の動作について警告する義務を負いません。 (そして実際、「未定義の振る舞い」イディオムを使用するプログラムが言語仕様によってコンパイラをクラッシュさせることが許可されている言語がいくつかあります!)...

対照的に、実装定義の動作を持つイディオムは、コンパイラー作成者が機能の実装方法についていくつかの選択肢を持っているため、1つを選択する必要がある動作です。名前が示すように、実装定義の動作は少なくとも定義されています。たとえば、C#では、整数の除算がオーバーフローしたときに、実装で例外をスローしたり、値を生成したりできますが、実装ではいずれかを選択する必要があります。ハードディスクを消去できません...

言語設計委員会が特定の言語イディオムを未定義または実装定義の動作のままにする要因は何ですか?

最初の主な要因は次のとおりです。特定のプログラムの動作に同意しない、市場に言語の実装が2つありますか?...

次の主な要因は次のとおりです:この機能は当然、実装の多くの異なる可能性を示しますが、そのいくつかは他のものより明らかに優れていますか?...

3番目の要因は次のとおりです。機能が複雑すぎて、その正確な動作の詳細な内訳を指定するのが困難または費用がかかるでしょうか?...

4番目の要因は次のとおりです:この機能は分析するコンパイラに高い負担をかけますか?...

5番目の要素は次のとおりです:この機能はランタイム環境に高い負荷をかけますか?...

6番目の要因は次のとおりです。定義された動作を作成すると、いくつかの主要な最適化が妨げられますか?...

これらは頭に浮かぶいくつかの要因にすぎません。もちろん、機能を「実装定義済み」または「未定義」にする前に、言語設計委員会が議論する他の多くの要素があります。

上記は非常に短い報道です。完全な記事には、この抜粋で言及されたポイントの説明と例が含まれています。それは読む価値があるmuchです。たとえば、「第6要素」の詳細は、Javaメモリモデル( JSR 1 ))の多くのステートメントの動機を洞察し、その理由を理解するのに役立ちます。一部の最適化は許可され、未定義の動作につながりますが、その他は禁止され、happen-before因果関係の要件などの制限につながります。

どの記事の資料も私にとって特に新しいものではありませんが、そのようなエレガントで簡潔で理解可能な方法で提示されているのを見たとしても、私はのろわれます。すごい。

18
gnat

頭の中で、少なくともC++と同じ意味ではなく、Javaに未定義の動作があるとは思いません。

この理由は、Javaの背後にはC++とは異なる哲学があるためです。Javaの中心的な設計目標は、プログラムをプラットフォーム間で変更せずに実行できるようにすることでした、仕様はすべてを非常に明示的に定義しています。

対照的に、CおよびC++の中心的な設計目標は効率です。つまり、たとえ必要がなくても、パフォーマンスを犠牲にする機能(プラットフォームの独立性を含む)はありません。この目的のために、仕様はいくつかの動作を意図的に定義していません。これらの動作を定義すると、一部のプラットフォームで余分な作業が発生し、特定のプラットフォーム向けのプログラムを作成し、そのすべての特異性を知っている人でもパフォーマンスが低下するためです。

Javaが制限された形式の未定義の動作を遡及的に導入することを余儀なくされたまさにその理由のための例もあります: strictfp キーワードがJava 1.2は、浮動小数点計算が仕様で以前に要求されたとおりにIEEE 754標準に正確に準拠しないようにするためです。これを行うと追加の作業が必要になり、一部の一般的なCPUではすべての浮動小数点計算が遅くなり、実際にはより悪い結果が生成されます。ある場合には。

10

Javaは、以前の言語の教訓が原因で、未定義の動作を根絶しようと懸命に取り組んでいます。たとえば、クラスレベルの変数は自動的に初期化されます。ローカル変数はパフォーマンス上の理由から自動初期化されませんが、これを検出できるプログラムをだれも作成できないようにする高度なデータフロー分析があります。参照はポインタではないため、無効な参照は存在できず、nullを逆参照すると特定の例外が発生します。

もちろん、完全に特定されていないいくつかの動作が残っており、それらがそうであると想定すれば、信頼できないプログラムを書くことができます。たとえば、通常の(並べ替えられていない)Setを反復する場合、言語は各要素が1回だけ表示されることを保証しますが、表示される順序は保証しません。順序は、連続する実行で同じになる場合もあれば、変わる場合もあります。または、他の割り当てが発生しない限り、またはJDKを更新しない限り、同じままである可​​能性があります。allのような影響を取り除くことはほぼ不可能です。たとえば、明示的に順序付けまたはランダム化する必要がありますallコレクション操作。これは、小さな追加のun-undefined-nessに値するものではありません。

3
Kilian Foth

「未定義の動作」とその起源を理解する必要があります。

未定義の動作は、標準で定義されていない動作を意味します。 C/C++には、非常に多くの異なるコンパイラー実装と追加機能があります。これらの追加機能により、コードがコンパイラに関連付けられました。これは、集中型の言語開発がなかったためです。そのため、一部のコンパイラの一部の高度な機能は「未定義の動作」になりました。

一方、Javaでは、言語仕様はSun-Oracleによって制御されており、仕様を作成しようとする人がいないため、未定義の動作はありません。

編集済み具体的に質問に答える

  1. 標準はコンパイラの前に作成されたため、Javaは未定義の動作から解放されています
  2. 最近のC/C++コンパイラでは実装の標準化が進んでいますが、標準化される前に実装された機能は、ISOがこれらの側面を維持しているため、「未定義の動作」としてタグ付けされたままです。
2
Sarvex

Javaは、C/C++にある未定義の動作をすべて本質的に排除します。 (例:符号付き整数オーバーフロー、ゼロ除算、初期化されていない変数、nullポインター逆参照、ビット幅を超えるシフト、ダブルフリー、「ソースコードの最後に改行なし」も。)しかし、Javaにはいくつかあります。プログラマーがめったに遭遇しない未定義の振る舞い。

  • Java Native Interface(JNI)、JavaがCまたはC++コードを呼び出す方法。 JNIを台無しにする方法はたくさんあります。たとえば、関数のシグネチャを誤って取得したり、JVMサービスへの無効な呼び出しを行ったり、メモリを破壊したり、データの割り当てや解放を誤ったりするなどです。以前にこれらの間違いを犯したことがあり、JNIコードを実行するいずれかのスレッドがエラーをコミットすると、通常、JVM全体がクラッシュします。

  • Thread.stop() 、これは非推奨です。見積もり:

    Thread.stopが廃止された理由

    それは本質的に安全ではないためです。スレッドを停止すると、スレッドがロックしているすべてのモニターのロックが解除されます。 (ThreadDeath例外がスタックを伝搬すると、モニターのロックが解除されます。)以前これらのモニターによって保護されていたオブジェクトのいずれかが不整合な状態であった場合、他のスレッドがこれらのオブジェクトを不整合な状態で表示する可能性があります。そのようなオブジェクトは損傷していると言われています。スレッドが損傷したオブジェクトを操作すると、任意の動作が発生する可能性があります。この動作は、微妙で検出が難しい場合や、顕著になる場合があります。他のチェックされていない例外とは異なり、ThreadDeathはスレッドをサイレントに強制終了します。したがって、ユーザーは自分のプログラムが破損している可能性があることを警告しません。破損は、実際の損傷が発生した後、いつでも、あるいは数時間または数日後にも発生します。

    https://docs.Oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

1
Nayuki