CとC++の未定義、未指定、実装定義の動作の違いは何ですか?
未定義の動作は、CおよびC++言語の側面の1つであり、プログラマーが来るのを驚くかも他の言語から(他の言語はより良く隠そうとします)。基本的に、多くのC++コンパイラがプログラムのエラーを報告しない場合でも、予測可能な方法で動作しないC++プログラムを作成できます。
古典的な例を見てみましょう:
#include <iostream>
int main()
{
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
変数p
は文字列リテラル"hello!\n"
を指し、以下の2つの割り当てはその文字列リテラルを変更しようとします。このプログラムは何をしますか? C++標準のセクション2.14.5段落11によれば、undefined behaviorを呼び出します:
文字列リテラルを変更しようとする効果は未定義です。
「しかし、これをコンパイルしても問題ありません。出力yellow
」または「未定義の文字列リテラルは読み取り専用メモリに格納されているので、最初の割り当て試行はコアダンプになります」という叫び声が聞こえます。 。これは、未定義の動作に関する問題です。基本的に、標準では、未定義の動作(鼻の悪魔さえ)を呼び出すと、何でも起こります。言語のメンタルモデルに従って「正しい」動作がある場合、そのモデルは単に間違っています。 C++標準には唯一の投票期間があります。
未定義の動作の他の例には、境界を超える配列へのアクセス、 nullポインターの逆参照 、 有効期間が終了した後のオブジェクトへのアクセス 、または 巧妙な表現と思われるi++ + ++i
のように。
C++標準のセクション1.9では、未定義の動作の2つの危険性の低い兄弟、unspecified behaviorおよびimplementation-defined behavior:
この国際規格のセマンティック記述は、パラメータ化された非決定的な抽象マシンを定義しています。
抽象マシンの特定の側面と操作は、この国際標準ではimplementation-defined(たとえば、
sizeof(int)
)として説明されています。これらは、抽象マシンのパラメーターを構成します。各実装には、これらの点での特性と動作を説明する文書が含まれます。抽象マシンの他の特定の側面と操作は、この国際規格ではunspecified(たとえば、関数への引数の評価の順序)として説明されています。可能な場合、この国際規格は許容される一連の動作を定義しています。これらは、抽象マシンの非決定的側面を定義します。
他の特定の操作は、この国際規格ではundefinedとして説明されています(たとえば、nullポインターの参照解除の効果)。 [注:この国際規格は、未定義の動作を含むプログラムの動作に要件を課していません。— メモの終了]
具体的には、セクション1.3.24には次のように記載されています。
許容される未定義の動作の範囲は、予測不可能な結果を伴う状況を完全に無視するから、環境に特徴的な文書化された方法での翻訳またはプログラム実行中の動作(ありまたはなし) (診断メッセージの発行)、翻訳または実行の終了(診断メッセージの発行)。
未定義の動作に陥るのを避けるために何ができますか?基本的に、あなたは 良いC++の本 を読んでいる必要があります。ねじインターネットチュートリアル。ねじブルズチャイルド。
まあ、これは基本的に標準からの単純なコピーアンドペーストです
.4.1 1 実装定義の動作各実装が選択方法を文書化する未指定の動作
2例実装定義の動作の例は、符号付き整数が右にシフトされたときの高位ビットの伝播です。
.4. 1 未定義の動作移植性のない、またはエラーのあるプログラム構造、またはこの国際規格が要件を課していないエラーのあるデータの使用時の動作
2注予期しない結果の可能性がある状況を完全に無視することから、翻訳またはプログラム実行中に環境に特徴的な文書化された方法で動作すること(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(診断メッセージの発行)。
3例未定義の動作の例は、整数オーバーフローの動作です。
.4.4 1 不特定の動作不特定の値の使用、またはこの国際規格が2つ以上の可能性を提供し、いかなる場合でも選択されるさらなる要件を課さない他の動作
2例指定されていない動作の例は、関数の引数が評価される順序です。
簡単な言葉遣いは、標準の厳密な定義よりも理解しやすいかもしれません。
実装定義の動作
言語には、データ型があると書かれています。コンパイラベンダーは、使用するサイズを指定し、実行した内容のドキュメントを提供します。
未定義の動作
あなたは何か間違ったことをしています。たとえば、int
に収まらないchar
に非常に大きな値があります。 char
にその値をどのように入れますか?実際には方法はありません!何でも起こりますが、最も賢明なことは、そのintの最初のバイトを取得してchar
に入れることです。最初のバイトを割り当てるためにそれを行うのは間違っていますが、それは内部で行われることです。
不特定の動作
これら2つの機能のうち、最初に実行されるのはどれですか?
void fun(int n, int m);
int fun1()
{
cout << "fun1";
return 1;
}
int fun2()
{
cout << "fun2";
return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?
言語は評価を左から右または右から左に指定しません!そのため、不特定の動作は未定義の動作をもたらす場合もあれば、生じない場合もありますが、プログラムは不特定の動作を生成すべきではありません。
@eSKayあなたの質問は答えを編集してさらに明確にする価値があると思います:)
fun(fun1(), fun2());
の動作は「実装定義」ではありませんか?結局のところ、コンパイラはどちらかのコースを選択する必要がありますか?
実装定義と未指定の違いは、コンパイラは最初のケースで動作を選択することになっているが、2番目のケースでは動作を選択する必要がないことです。たとえば、実装にはsizeof(int)
の定義が1つだけ必要です。したがって、プログラムのある部分ではsizeof(int)
が4で、他の部分では8であるとは言えません。コンパイラがOKと言うことができる不特定の動作とは異なり、これらの引数を左から右に評価し、次の関数の引数は右から左に評価します。同じプログラムで発生する可能性があるため、unspecifiedと呼ばれる理由です。実際、未指定の動作の一部が指定されていれば、C++をより簡単にすることができました。こちらをご覧ください Dr。Stroustrupの答え :
コンパイラーにこの自由度を与え、「通常の左から右への評価」を要求することで生成できるものとの違いは重要であると主張されています。私は納得していませんが、無数のコンパイラが自由を利用しており、一部の人々が自由を熱心に擁護しているため、変更は困難であり、CおよびC++の世界の隅々まで浸透するには数十年かかる場合があります。すべてのコンパイラが++ i + i ++などのコードに対して警告するわけではないことに失望しています。同様に、引数の評価の順序は指定されていません。
IMOはあまりにも多くの "もの"が未定義、未指定、実装定義などのままになっています。しかし、それは言うのは簡単であり、例を挙げることさえできますが、修正は困難です。また、ほとんどの問題を回避して移植可能なコードを生成することはそれほど難しくないことにも注意してください。
公式C Rationale Documentから
用語未指定動作、未定義動作、および実装定義動作は、標準がプロパティを持たないプログラムを作成した結果を分類するために使用されます。または完全に説明することはできません。この分類を採用する目的は、実装の品質が市場での積極的な力になることを可能にする実装の特定の多様性を可能にすることと、標準への適合の隠蔽を削除することなく、特定の人気のある拡張を可能にすることです。標準の付録Fでは、これら3つのカテゴリのいずれかに該当する動作をカタログしています。
指定されていない動作は、実装者にプログラムの翻訳の自由度を与えます。この許容範囲は、プログラムの翻訳に失敗する限りは拡大しません。
未定義の動作は、実装者に、診断が困難な特定のプログラムエラーをキャッチしないライセンスを与えます。また、準拠する可能性のある言語拡張の領域も識別します。実装者は、公式に定義されていない動作の定義を提供することにより、言語を拡張できます。
実装定義動作により、実装者は適切なアプローチを自由に選択できますが、この選択をユーザーに説明する必要があります。実装定義として指定された動作は、通常、ユーザーが実装定義に基づいて意味のあるコーディング決定を行うことができる動作です。実装者は、実装定義がどれほど広範囲にあるべきかを決定するとき、この基準に留意する必要があります。不特定の動作と同様に、実装定義の動作を含むソースの翻訳に失敗するだけでは、適切な応答ではありません。
未定義の動作と未指定の動作 に簡単な説明があります。
最終的な要約:
要約すると、不特定の動作は通常、ソフトウェアに移植性が必要でない限り、心配する必要のないものです。逆に、未定義の動作は常に望ましくなく、発生することはありません。
歴史的には、実装定義の動作と未定義の動作の両方は、標準の作成者が、品質の実装を作成する人々が判断を使用して、実行される意図されたアプリケーション分野のプログラムに役立つ動作保証があればそれを決定することを期待する状況を表しています意図したターゲット。ハイエンドの数値演算コードのニーズは、低レベルのシステムコードのニーズとはまったく異なり、UBとIDBの両方がコンパイラライターにこれらのさまざまなニーズを満たす柔軟性を与えます。どちらのカテゴリも、実装が特定の目的に有用な方法で動作することを強制するものではなく、あらゆる目的に使用することさえ要求しません。ただし、特定の目的に適していると主張する品質の実装は、そのような目的に適した方法で動作する必要があります規格がそれを要求するかどうか。
Implementation-Defined BehaviorとUndefined Behaviorの唯一の違いは、前者では実装が一貫した動作を定義および文書化することを要求することです実装が役に立たないかもしれない場合でも。それらの間の境界線は、実装が動作を定義するのに一般に役立つかどうかではありません(コンパイラの作成者は、標準が要求するかどうかに関係なく、実用的な場合に有用な動作を定義する必要があります)が、動作を定義する実装があるかどうかコストがかかり、無駄になります。そのような実装が存在する可能性があるという判断は、他のプラットフォームで定義された動作をサポートすることの有用性に関する判断を意味するものではありません。
残念ながら、1990年代半ば以降、コンパイラライターは、動作のマンデートの欠如を、動作の保証は重要なアプリケーション分野でも、実際にはコストがかからないシステムでもコストに見合わないと判断するものとして解釈し始めました。 UBを合理的な判断を促す招待状として扱う代わりに、コンパイラの作成者は、UBをnotの言い訳として扱い始めました。
たとえば、次のコードを考えます:
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
2の補数の実装は、式v << pow
をv
が正であるか負であるかに関係なく、2の補数シフトとして扱うために努力する必要はまったくありません。
ただし、今日のコンパイラライターの一部が好む哲学では、v
が負の値になるのは、プログラムが未定義の動作に従事する場合のみであるため、プログラムにv
の負の範囲をクリップさせる理由はありません。負の値の左シフトはかつて重要なすべてのコンパイラでサポートされていましたが、既存のコードの多くはその動作に依存していましたが、現代の哲学は標準が左シフトの負の値はUBであると言っているという事実を解釈しますコンパイラの作成者はそれを無視しても構いません。
定義された実装
実装者は、十分に文書化され、標準が選択肢を提供することを望んでいますが、必ずコンパイルしてください
指定なし-
実装定義と同じですが、文書化されていません
未定義-
何が起こるかもしれません、それの世話をします。
C++標準n3337 § 1.3.10 実装定義の動作
動作、適切な形式のプログラム構成と正しいデータの場合、実装に依存し、各実装が文書化する
C++標準では、特定の構成に特定の動作を課さない場合もありますが、代わりに特定の実装(ライブラリのバージョン)によって、明確に定義された特定の動作を選択し、記述を選択する必要があります。そのため、Standardはこれを説明していませんが、ユーザーはプログラムの動作を正確に知ることができます。
C++標準n3337 § 1.3.24 未定義の動作
この国際標準が要件を課さない動作[注:この国際標準が動作の明示的な定義を省略した場合、またはプログラムが誤った構造または誤ったデータを使用した場合、未定義の動作が予想される。許容される未定義の動作は、予測不可能な結果で完全に状況を無視することから、環境に特徴的な文書化された方法での動作中の動作(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(発行による)診断メッセージの)。多くの誤ったプログラム構造は、未定義の動作を引き起こしません。それらは診断される必要があります。 —終了ノート]
プログラムがC++標準に従って定義されていないコンストラクトに遭遇した場合、やりたいことは何でもできます(私にメールを送信するか、あなたにメールを送信するか、コードを完全に無視します)。
C++標準n3337 § 1.3.25 不特定の動作
動作、整形式のプログラム構成と正しいデータの場合、実装に依存します考えられる動作の範囲は、通常、この国際規格で規定されています。 —終了ノート]
C++ Standardは、一部の構成に特定の動作を強制しませんが、代わりに、特定の実装(ライブラリのバージョン)によって、特定の明確に定義された動作を選択する必要があります(ボットは説明不要)。そのため、説明が提供されていない場合、ユーザーがプログラムの動作を正確に知ることは困難です。