未定義の振る舞いを呼び出す、不十分に記述されたC ++ソースコードをコンパイルしようとすると、(彼らが言うように)「何でも起こり得る」としましょう。
C ++言語仕様が「準拠」コンパイラで許容できると見なすものの観点から、このシナリオの「すべて」には、コンパイラのクラッシュ(またはパスワードの盗用、またはコンパイル時の誤動作やエラーアウト)が含まれますか。未定義の振る舞いの範囲は、結果の実行可能ファイルが実行されたときに何が起こり得るかに特に限定されていますか?
未定義の振る舞いを呼び出す、不十分に記述されたC ++ソースコードをコンパイルしようとすると、(彼らが言うように)「何でも起こり得る」としましょう。
C ++言語仕様が「準拠」コンパイラで許容できると見なすものの観点から、このシナリオの「すべて」には、コンパイラのクラッシュ(またはパスワードの盗用、またはコンパイル時の誤動作やエラーアウト)が含まれますか。未定義の振る舞いの範囲は、結果の実行可能ファイルが実行されたときに何が起こり得るかに特に限定されていますか?
回答:
未定義の振る舞いの規範的な定義は次のとおりです。
この国際規格が要件を課していない行動
[注:この国際規格で動作の明示的な定義が省略されている場合、またはプログラムが誤った構成または誤ったデータを使用している場合、未定義の動作が予想される場合があります。許容される未定義の動作は、予測できない結果で状況を完全に無視することから、環境に特徴的な文書化された方法で翻訳またはプログラムの実行中に動作すること(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(発行あり)にまで及びます。診断メッセージの)。多くの誤ったプログラム構成は、未定義の動作を引き起こしません。それらは診断される必要があります。定数式の評価は、未定義として明示的に指定された動作を示すことはありません。—エンドノート]
メモ自体は規範的ではありませんが、実装が示すことが知られているさまざまな動作について説明しています。したがって、コンパイラのクラッシュ(翻訳が突然終了する)は、そのメモによると正当です。しかし実際には、規範的なテキストが言うように、標準は実行または翻訳のいずれにも制限を設けていません。実装がパスワードを盗んだとしても、それは標準に定められた契約に違反するものではありません。
NULL-derefやゼロ除算など、私たちが通常心配するほとんどの種類のUBは、ランタイムUBです。ランタイムUBを引き起こす機能のコンパイル実行された場合、コンパイラがクラッシュしないようにしなければならない原因。 多分それが関数(そして関数を通るそのパス)が間違いなくそうすることを証明できない限りプログラムによって実行されます。
(2番目の考え:コンパイル時にtemplate / constexprが評価を必要とするとは考えていなかったかもしれません。おそらく、その間のUBは、結果の関数が呼び出されなくても、変換中に任意の奇妙さを引き起こす可能性があります。)
@StoryTellerの回答のISOC ++引用の翻訳部分の動作は、ISOC標準で使用されている言語と似ています。Cにはテンプレートが含まれていないかconstexpr
、コンパイル時必須のeval。
しかし、面白い事実:ISO Cは、翻訳が終了した場合、診断メッセージが必要であるとメモで述べています。または「翻訳中に...文書化された方法で動作する」。「状況を完全に無視する」ということは、翻訳をやめることを含むとは言えないと思います。
翻訳時間UBについて学ぶ前に書かれた古い答え。 ただし、これはランタイムUBにも当てはまり、したがって、潜在的には依然として有用です。
コンパイル時に発生するUBのようなものはありません。これは、特定の実行パスに沿ってコンパイラーに表示されますが、C ++の用語では、実行が関数を介してその実行パスに到達するまで発生しませんでした。
コンパイルすら不可能にするプログラムの欠陥はUBではなく、構文エラーです。このようなプログラムは、C ++の用語では「整形式ではありません」(標準が正しい場合)。プログラムは整形式である可能性がありますが、UBが含まれています。 未定義の振る舞いと不正な形式の違い、診断メッセージは必要ありません
私が何かを誤解していない限り、実行がゼロ除算に達することは決してないため、ISO C ++ではこのプログラムを正しくコンパイルして実行する必要があります。(実際には(Godbolt)、優れたコンパイラーは動作する実行可能ファイルを作成するだけです。gcc / clangはx / 0
、最適化する場合でも、これについて警告しますが、これは警告しません。しかし、とにかく、ISO C ++がどれほど低い実装の品質を可能にするかを伝えようとしています。 / clangは、プログラムを正しく記述したことを確認する以外に、ほとんど有用なテストではありません。)
int cause_UB() {
int x=0;
return 1 / x; // UB if ever reached.
// Note I'm avoiding x/0 in case that counts as translation time UB.
// UB still obvious when optimizing across statements, though.
}
int main(){
if (0)
cause_UB();
}
このユースケースには、Cプリプロセッサ、またはconstexpr
変数とそれらの変数の分岐が含まれる場合があります。これにより、定数の選択では到達できないパスが意味をなさなくなります。
コンパイル時に表示されるUBを引き起こす実行パスは、決して取られないと想定できます。たとえば、x86のコンパイラはud2
、の定義として(不正な命令例外を引き起こす)を発行する可能性がありcause_UB()
ます。または、関数内で、片側が証明可能なUBにif()
つながる場合は、ブランチを削除できます。
しかし、コンパイラーはそれでも他のすべてを正気で正しい方法でコンパイルする必要があります。すべてのパスない出会いを(または出会いに証明することはできません)UBはまだ実行として-場合C ++抽象機械がそれを実行していたことをASMにコンパイルする必要があります。
無条件のコンパイル時に表示されるUBmain
は、このルールの例外であると主張することができます。 または、コンパイル時に実行可能であることが証明可能main
が実際に保証されたUBに到達することます。
コンパイラの正当な動作には、実行すると爆発する手榴弾の生成が含まれると私はまだ主張します。またはもっともっともらしいのは、その定義がmain
単一の違法な指示で構成されていることです。 プログラムを実行したことがなければ、UBはまだ存在していないと思います。 コンパイラ自体は爆発することを許可されていません、IMO。
ブランチ内に可能なまたは証明可能なUBを含む関数
任意の実行パスに沿ったUBは、以前のすべてのコードを「汚染」するために時間的に後方に到達します。しかし実際には、コンパイラーは、実行パスがコンパイル時に表示されるUBにつながることを実際に証明できる場合にのみ、そのルールを利用できます。例えば
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
コンパイラは、INT_MINおよびINT_MAXで符号付きオーバーフローUBx
がx * 5
発生するポイントまで、3以外のすべてで機能するasmを作成する必要があります。この関数がで呼び出されない場合x==3
、プログラムにはもちろんUBが含まれておらず、記述どおりに機能する必要があります。
間違いなく3if(x == 3) __builtin_unreachable();
でx
はないことをコンパイラーに伝えるためにGNUCで書いた方がよいでしょう。
実際には、通常のプログラムのいたるところに「地雷原」コードがあります。たとえば、整数による除算は、コンパイラにゼロ以外であることを約束します。ポインタderefは、コンパイラにNULL以外であることを約束します。
ここで「合法」とはどういう意味ですか?これらの標準によれば、C標準またはC ++標準と矛盾しないものはすべて合法です。あなたが声明を実行i = i++;
し、その結果恐竜が世界を支配する場合、それは基準と矛盾しません。しかし、それは物理法則と矛盾するので、起こりません:-)
未定義の動作でコンパイラがクラッシュしても、CまたはC ++標準に違反していません。ただし、コンパイラの品質を改善できる(そしておそらく改善する必要がある)ことを意味します。
C標準の以前のバージョンでは、エラーであるか、未定義の動作に依存しないステートメントがありました。
char* p = 1 / 0;
char *に定数0を割り当てることができます。ゼロ以外の定数を許可することはできません。1/0の値は未定義の動作であるため、コンパイラがこのステートメントを受け入れるかどうかは未定義の動作です。(現在、1/0は「整数定数式」の定義を満たしていません)。
標準は、実装が発生し#include "'foo'"
た場合の動作に要件を課しません。コンパイラの作成者が、出力を一時ファイルに向けて指定されたプログラムを実行し、そのファイルのように動作することにより、その形式のインクルードディレクティブ(ファイル名にアポストロフを含む)を処理することが有用であると判断した場合は#include
、試行します。上記の行を含むプログラムを処理すると、プログラムが実行されfoo
、結果がどうなるかがわかります。
したがって、Cプログラムを実行しようと努力しなくても、Cプログラムを翻訳しようとした結果として何が起こるかについては、一般に制限はありません。