12

未定義の動作とシーケンス ポイント (未定義の動作とシーケンス ポイントなど)に関するいくつかの非常に良い回答を読み、理解しました。

   int i = 1;
   a = i + i++; //this is undefined behaviour

C++ 標準によると、未定義のコードです。しかし、それが未定義の動作であることの背後にあるより深い理由は何ですか? 不特定の動作にするだけで十分ではないでしょうか? 通常の議論は、シーケンス ポイントが少ないことにより、C++ コンパイラはさまざまなアーキテクチャに対してより適切に最適化できるが、未指定のままにしておくと、それらの最適化も許可されないのではないかというものです。の

   a = foo(bar(1), bar(2)); //this is unspecified behaviour

コンパイラも最適化でき、未定義の動作ではありません。最初の例では、a が 2 または 3 のいずれかであることは明らかであるため、セマンティクスは私には明らかです。なぜ特定されていないものと定義されていないものがあるのか​​、理由があることを願っています。

4

5 に答える 5

9

これらの最適化のすべてではありません。たとえば、Itanium は加算とインクリメントの両方を並行して実行でき、たとえば、このようなことをしようとするとハードウェア例外が発生する可能性があります。

しかし、これは完全にマイクロ最適化であり、それを利用するコンパイラを作成することは非常に困難であり、それを実行できる非常にまれなアーキテクチャです (IIRC は当時存在しませんでした。ほとんどが仮説でした)。したがって、現実には、2012 年の時点で、明確に定義された動作ではない理由はありません。実際、C++11 では、これらの状況がより明確に定義されています。

于 2012-09-24T20:47:47.427 に答える
3

C++ の観点からは、答えは信じられないほど単純だと思います。C がずっと前に未定義の動作を行ったため、未定義の動作になりました。それを変更しても本質的に潜在的な利益はありませんでした。

それは、私が推測することは、実際にはより意図された質問であったことを示しています:なぜCはこの未定義の動作をしたのですか?

それほど単純な答えはないと思います。1 つの可能性は単純な注意です。C 標準が作成されるまでに、C はすでに実装され、展開され、多くのマシンで使用されていたという知識です。当時のかなりの数のマシンは、私が今でも見ている多くのコードのように思えました: 元々は個人的な実験としてのみ設計されたもので、十分に機能したため、最終的に「製品」として指定されました。最も深刻な問題。そのため、これが壊れるハードウェアを誰も知らなかったとしても、そのようなハードウェアが存在しないことを本当に確信できる人は誰もいなかったので、単に UB と呼んでそれで終了するのが最も安全でした.

別の可能性は、単純な注意を少し超えたということです。現代のハードウェアではかなり安全だと感じることができますが、当時、これに大きな問題があることを人々が本当に知っていたハードウェアがあった可能性があり、(特にそのハードウェアに関連するベンダーが委員会に代表されていた場合) C を実行できるそのハードウェアは重要であると考えられていました。

さらに別の可能性としては、これが壊れる可能性のある既存の実装を誰も知らなかった (またはその可能性を恐れていなかった) にもかかわらず、何かが壊れる可能性があるという将来の可能性を予見していたので、未定義の動作が将来の保証の方法と見なされた可能性があります。少なくともある程度の言語。

最後の可能性は、標準のその部分を書いていた人は誰でも、許容できると思われる一連のルールを思い付くとすぐに他のことに移ったということです。より良い。

推測する必要があるとすれば、それはおそらく、私が示した 3 番目と 4 番目の可能性の組み合わせだったと言えます。これを書いたとき、実装の部分で自由度を最大化することが、コンセンサスを得るための最も簡単で簡単なルートのように思えたので、彼らはそれを完了して、より大きくより良いものに移ることができました.

于 2012-09-24T21:53:45.573 に答える
2

未定義の動作と未指定の動作には大きな違いがあります。未指定の動作は整形式 (つまり、合法的) ですが、標準では、実装に関してコンパイラ ベンダーにある程度の裁量が与えられています。未定義の動作は、構文的に正しいように見える残虐行為です。動作を完全に違法 (コンパイラが拒否しなければならないもの) ではなく「未定義」と見なす主な理由は、未定義の動作を診断するのが非常に難しい場合があるためです。

于 2012-09-24T20:59:46.177 に答える
0
a = foo(bar(1), bar(2)); // this is unspecified behaviour

2 つの関数呼び出しは任意の順序で行うことができますが、2 つの異なる関数呼び出しのままです。呼び出しがインライン化されていても、マシン命令はオーバーラップできません。実際には、それらはかなりオーバーラップしますが、オプティマイザーは、関数呼び出しが個別であるかのように厳密に動作するコードを生成するように制限されています。

a = i + i++; // this is undefined behaviour

スカラー i では、分離の要件はありません。i からフェッチする CPU 命令、加算する CPU 命令、ポストインクリメントを行う命令は自由に混ざり合い、オプティマイザは i が左側にあり i が左側にあることを知らないふりをすることができます。右は同じものです。この前提条件に違反した場合に、どのような種類の壊れたアセンブリが生成されるかはわかりません。したがって、未定義。

于 2012-09-24T20:58:47.253 に答える
0

MIPS 1 は、Load Delay スロットを備えた合理的な実装でした。ロードの実行はすぐにはできません。結果は、次の命令が開始された後にのみ表示されました。コンパイラにとって、これは大したことではありませんでした。無関係な命令を次のスロットに入れるだけです。

もちろん、コンパイラは「無関係」とは何かを知る必要がありました。単一の変数の同時変更に対する C の規則により、コンパイラーは、無関係であるべき命令を見つける際にはるかに多くの選択肢がありました。2 つの操作が 1 つのステートメントに含まれている場合、それらは異なる変数に対して操作する必要があるため、関連性はありません。

于 2012-09-25T08:44:45.137 に答える