Javaの未定義の動作


14

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

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

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


4
Javaは非常に厳密に定義されています。Java言語仕様を確認してください。


4
@ user1249、「未定義の動作」も実際にはかなり厳密に定義されています。
Pacerier 14年


「契約」に違反した場合、Javaは何と言いますか?.equalsをオーバーロードして.hashCodeと互換性がない場合に発生しますか?docs.oracle.com/javase/7/docs/api/java/lang/…これ は口語的には未定義ですが、技術的にはC ++と同じではありませんか?
Mooingダック

回答:


18

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

Java 7 JLSは、17.4.8で「未定義」という単語を1回使用します。実行と因果関係の要件

私たちは、使用f|dのドメイン制限することにより、与えられた機能を示すためにfにをd。すべての場合xではdf|d(x) = f(x)すべてのために、とxではないdf|d(x)され未定義 ...

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

特定の引数が範囲外の場合、結果は未定義です...

ExecutorService.invokeAll(Collection)状態のJavadoc :

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

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

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


付録

質問のコメントの 1つは、Eric Lippertによるトピックの問題への有用な紹介を提供する記事、実装定義の動作を参照しています。

著者はJavaではなくC#を対象とすることを心に留めておく必要がありますが、言語にとらわれない理由でこの記事をお勧めします。

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

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

言語設計委員会が特定の言語イディオムを未定義または実装定義の振る舞いのままにしておく要因は何ですか?

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

次の主な要因は次のとおりです。この機能は、実装のさまざまな可能性を自然に提示しますか。...

3番目の要因は次のとおりです。機能は非常に複雑なため、正確な動作を詳細に分類するのは難しいか、指定するのに費用がかかりますか?...

4番目の要因は、この機能がコンパイラーに高い負荷を課して分析するかどうかです。...

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

第六の要因は次のとおりです。行動に定義排除してにいくつかの主要な最適化を行っていますか?...

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

上記は非常に短い取材です。記事全体には、この抜粋で言及したポイントの説明と例が含まれています。それは非常に価値の読み。たとえば、「6番目の要因」に与えられた詳細は、Javaメモリモデル(JSR 133)の多くのステートメントの動機に関する洞察を与え、いくつかの最適化が許可される理由を理解するのに役立ちます。前発生因果関係の要件などの制限。

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


JMM!=基盤となるハードウェアと、並行性に関する実行プログラムの最終結果は、WinIntelとSolarisの場合とは異なる可能性があることを追加します
Martijn Verburg

2
@MartijnVerburgそれはかなり良い点です。私は「未定義」としてそれにタグを付けることを躊躇唯一の理由は、そのメモリモデルのポーズ制約が好きで起こる、前の因果関係を正しく同期プログラムの実行に
ブヨ

確かに、仕様はJMMの下での動作を定義しますが、Intelなどは必ずしも同意するわけではありません
;

@MartijnVerburg JMMの主なポイントは、「意見が異なる」プロセッサメーカーからの最適化漏れを防ぐことだと思います。私の知る限りでは、Java 5.0の前にボンネットの下に行われ、投機的な書き込みは「空中から」のようなプログラムに漏れる可能性が12月アルファ、と頭痛のこの種の持っていた理解として-したがって、因果関係の要件はJSR 133(JMM)に入ったが
ブヨ

9
@MartinVerburg-サポートされているハードウェアプラットフォーム上で、JVMがJLS / JMM仕様に従って動作することを確認するのは、JVM実装者の仕事です。異なるハードウェアの動作が異なる場合、それを処理し、機能させるのはJVM実装者の仕事です。
スティーブンC

10

少なくとも、C ++と同じ意味では、Javaには未定義の動作はないと思います。

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

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

まさにその理由で、Javaが限定的な未定義の動作を遡及的に導入することを強制された例さえあります:strictfpキーワードはJava 1.2で導入され、浮動小数点計算が仕様が以前に要求していたIEEE 754標準に従うことから逸脱することを可能にしますなぜなら、そうすると余分な作業が必要になり、一部の一般的なCPUですべての浮動小数点計算が遅くなりますが、実際には場合によってはより悪い結果を生むからです。


2
Javaのもう1つの主要な目標であるセキュリティと分離に注意することが重要だと思います。これも、「C ++の場合のように」「未定義の」動作がない理由だと思います。
K.ステフ

3
@ K.Steff:ハイパーモダンC / C ++は、リモートでセキュリティに関連するものにはまったく不適切です。int x=-1; foo(); x<<=1;ハイパーモダンの哲学を考えると、書き直しが優先されるfooため、終了しないパスは到達不能でなければなりません。これfooif (should_launch_missiles) { launch_missiles(); exit(1); }、コンパイラがif である場合、それを単純化することができます(そして、一部の人々はそうすべきです)launch_missiles(); exit(1);。従来のUBはランダムなコード実行でしたが、これは時間と因果律の法則に縛られていました。改良された新しいUBはどちらにも拘束されません。
supercat

3

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

もちろん、完全に指定されていない動作がいくつか残っており、それらがそうであると仮定すれば、信頼できないプログラムを書くことができます。たとえば、通常の(ソートされていない)を反復処理する場合Set、言語は各要素を一度だけ表示することを保証しますが、表示される順序は保証しません。連続した実行で順序は同じかもしれませんし、変わるかもしれません。または、他の割り当てが発生しない限り、またはJDKなどを更新しない限り、同じままである可​​能性があります。そのような効果をすべて取り除くことはほぼ不可能です。たとえば、すべてのコレクション操作を明示的に順序付けまたはランダム化する必要がありますが、これは小さな追加の未定義ネスの価値はありません。


参照は別の名前のポインターです
-curiousguy

@curiousguy-「参照」は一般に、数値の算術操作の使用を許可しないと想定されます。これは「ポインター」にしばしば許可されます。したがって、前者は後者よりも安全な構造です。オブジェクトへの有効な参照が存在する間、オブジェクトのストレージを再利用できないメモリ管理システムと組み合わせることで、参照はメモリ使用エラーを防ぎます。適切なメモリ管理が使用されている場合でも、ポインタはそうすることができません。
ジュール

@Julesそれから用語の問題です。あるものをポインターまたはリファレンスと呼び、「安全な」言語では「リファレンス」を使用し、ポインター演算および手動メモリー管理の使用を許可する言語では「ポインター」を使用することができます。(AFAIKの「ポインター演算」はC / C ++でのみ行われます。)
curiousguy

2

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

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

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

質問に具体的に答えて編集

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

2
JavaにはUBがないことは正しいかもしれませんが、1つのエンティティがすべてを制御している場合でも、UBを使用する理由があるかもしれないので、あなたが与えた理由は結論につながりません。
AProgrammer

2
また、CとC ++の両方がISOによって標準化されています。複数のコンパイラが存在する場合がありますが、一度に1つの標準しかありません。
MSalters

1
@SarvexJatasra、これがUBの唯一のソースであることに同意しません。たとえば、1つのUBがダングリングポインターを逆参照しているため、仕様を開始したとしても、GCを持たない言語でUBを残すのには十分な理由があります。そして、これらの理由は、既存の慣行や既存のコンパイラとは関係ありません。
AProgrammer

2
@SarvexJatasra、符号付きオーバーフローはUBであり、これは規格が明示的にそう言っているためです(UBの定義で与えられた例ですら)。無効なポインターを逆参照することも、同じ理由でUBである、と標準は言っています。
AProgrammer

2
@ bames53:引用された利点のどれも、ハイパーモダンコンパイラがUBで取っている緯度のレベルを必要としません。範囲外のメモリアクセスとスタックオーバーフローを除き、「自然に」ランダムなコードの実行を誘発する可能性があるため、ほとんどのUBのような操作が不確定な結果をもたらすと言うよりも広い範囲を必要とする有用な最適化は考えられません値(「余分なビット」があるかのように動作する可能性があります)実装のドキュメントが明示的にそのようなものを課す権利を留保している場合にのみ、それ以上の結果をもたらす可能性があります。ドキュメントは「制約のない動作」を与えることがあります
...-supercat

1

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

  • Java Native Interface(JNI)、JavaがCまたはC ++コードを呼び出す方法。関数の署名を間違えたり、JVMサービスへの無効な呼び出しを行ったり、メモリを破損したり、不正なものを割り当てたり解放したりするなど、JNIを台無しにする多くの方法があります。以前にこれらの間違いを犯しましたが、一般に、JNIコードを実行する1つのスレッドがエラーをコミットすると、JVM全体がクラッシュします。

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

    なぜThread.stop非推奨ですか?

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.