未定義の動作を含むソースコードがコンパイラをクラッシュさせることは合法ですか?


85

未定義の振る舞いを呼び出す、不十分に記述されたC ++ソースコードをコンパイルしようとすると、(彼らが言うように)「何でも起こり得る」としましょう。

C ++言語仕様が「準拠」コンパイラで許容できると見なすものの観点から、このシナリオの「すべて」には、コンパイラのクラッシュ(またはパスワードの盗用、またはコンパイル時の誤動作やエラーアウト)が含まれますか。未定義の振る舞いの範囲は、結果の実行可能ファイルが実行されたときに何が起こり得るかに特に限定されていますか?


22
「UBはUBです。それと一緒に生きてください」...待てない。「MCVEを投稿してください。」...待てない。私はそれが不適切に引き起こすすべての反射神経についての質問が大好きです。:-)
Yunnosch

14
本当に制限はありません。それが、UBが鼻の悪魔を召喚できると言われている理由です。
一部のプログラマーは

15
UBは作者にSOに質問を投稿させることができます。:P
TanveerBadar19年

46
C ++標準の内容に関係なく、私がコンパイラーの作成者であれば、コンパイラーのバグと見なすでしょう。したがって、これが表示されている場合は、欠陥レポートを提出してください。
ジョン

9
@LeifWillertsこれは80年代に戻った。正確な構成は覚えていませんが、複雑な変数型を使用することにかかっていると思います。交換品を入れた後、「何を考えていたのか、物事がそのように機能しない」という瞬間がありました。マシンを再起動するためだけに、構成を拒否したことでコンパイラを非難しませんでした。今日、誰もがそのコンパイラに遭遇することはないと思います。これは、68000マイクロプロセッサを対象としたHP64000用のHPCクロスコンパイラでした。
AviBerger19年

回答:


71

未定義の振る舞いの規範的な定義は次のとおりです。

[defns.undefined]

この国際規格が要件を課していない行動

[注:この国際規格で動作の明示的な定義が省略されている場合、またはプログラムが誤った構成または誤ったデータを使用している場合、未定義の動作が予想される場合があります。許容される未定義の動作は、予測できない結果で状況を完全に無視することから、環境に特徴的な文書化された方法で翻訳またはプログラムの実行中に動作すること(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(発行あり)にまで及びます。診断メッセージの)。多くの誤ったプログラム構成は、未定義の動作を引き起こしません。それらは診断される必要があります。定数式の評価は、未定義として明示的に指定された動作を示すことはありません。—エンドノート]

メモ自体は規範的ではありませんが、実装が示すことが知られているさまざまな動作について説明しています。したがって、コンパイラのクラッシュ(翻訳が突然終了する)は、そのメモによると正当です。しかし実際には、規範的なテキストが言うように、標準は実行または翻訳のいずれにも制限を設けていません。実装がパスワードを盗んだとしても、それは標準に定められた契約に違反するものではありません。


43
それはあなたのことができれば、と述べ、実際にコンパイル時に任意のコードを実行するようにコンパイラを取得し、任意のサンドボックスなしで、その後、さまざまなセキュリティの人々は次のようになり非常にそれについて知って興味を持って。コンパイラのセグフォールトについても同じことが言えます。
ケビン

67
ケビンが言ったことについても同じです。以前のキャリアのC / C ++ / etcコンパイラエンジニアとして、私たちの立場は、未定義の動作がプログラムをクラッシュさたり、出力データを台無しにしたり、家に火をつけたりする可能性があるというものでした。ただし、入力が何であっても、コンパイラがクラッシュすることはありません。(有用なエラーメッセージが表示されない場合がありますが、CTHULHU TAKE THE WHEELとsegfaultingを叫ぶだけでなく、何らかの診断と終了を生成する必要があります。)
TiStrga19年

8
@TiStrgaクトゥルフは素晴らしいF1ドライバーになると思います。
ゼータバンド

35
「実装がパスワードを盗んだとしても、それは標準に定められた契約に違反するものではありません。」コードにUBがあるかどうかに関係なく、それは本当ですよね?標準は、コンパイルされたプログラムが何をすべきかを指示するだけです-コードを正しくコンパイルするが、その過程でパスワードを盗むコンパイラは、標準に違反しません。
カルマイスター

8
@ Carmeister、oooh、それは良い点です。「UBがコンパイラに核戦争を開始する許可を与える」という議論が出てきたら、必ずそのことを人々に思い出させます。再び。
ilkkachu

8

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で符号付きオーバーフローUBxx * 5発生するポイントまで、3以外のすべてで機能するasmを作成する必要があります。この関数がで呼び出されない場合x==3、プログラムにはもちろんUBが含まれておらず、記述どおりに機能する必要があります。

間違いなく3if(x == 3) __builtin_unreachable();xはないことをコンパイラーに伝えるためにGNUCで書いた方がよいでしょう。

実際には、通常のプログラムのいたるところに「地雷原」コードがあります。たとえば、整数による除算は、コンパイラにゼロ以外であることを約束します。ポインタderefは、コンパイラにNULL以外であることを約束します。


3

ここで「合法」とはどういう意味ですか?これらの標準によれば、C標準またはC ++標準と矛盾しないものはすべて合法です。あなたが声明を実行i = i++;し、その結果恐竜が世界を支配する場合、それは基準と矛盾しません。しかし、それは物理法則と矛盾するので、起こりません:-)

未定義の動作でコンパイラがクラッシュしても、CまたはC ++標準に違反していません。ただし、コンパイラの品質を改善できる(そしておそらく改善する必要がある)ことを意味します。

C標準の以前のバージョンでは、エラーであるか、未定義の動作に依存しないステートメントがありました。

char* p = 1 / 0;

char *に定数0を割り当てることができます。ゼロ以外の定数を許可することはできません。1/0の値は未定義の動作であるため、コンパイラがこのステートメントを受け入れるかどうかは未定義の動作です。(現在、1/0は「整数定数式」の定義を満たしていません)。


4
正確に言うと、世界を支配する恐竜は、物理法則(ジュラシックパークのバリエーションなど)と矛盾しません。可能性は非常に低いです。:)
気まぐれな

-1

標準は、実装が発生し#include "'foo'"た場合の動作に要件を課しません。コンパイラの作成者が、出力を一時ファイルに向けて指定されたプログラムを実行し、そのファイルのように動作することにより、その形式のインクルードディレクティブ(ファイル名にアポストロフを含む)を処理することが有用であると判断した場合は#include、試行します。上記の行を含むプログラムを処理すると、プログラムが実行されfoo、結果がどうなるかがわかります。

したがって、Cプログラムを実行しようと努力しなくても、Cプログラムを翻訳しようとした結果として何が起こるかについては、一般に制限はありません。


プログラミング言語の翻訳者やコンパイラについても同じことが言えます。または、そのことについては、どんなプログラムでも。
ロバートハーベイ

@RobertHarvey:多くのプログラミング言語の仕様は、そのようなことについてはるかに具体的です。言語仕様で、特定のディレクティブがOSパスが指定されたとおりのストリームから入力を読み取ると述べており、OSが特定のパスを読み取るときに奇妙なことを行う場合、それは言語仕様の制御外になりますが、私はしませんほとんどの言語仕様では、動作を定義するプラットフォームであっても、そのようなディレクティブを文書化することなく、自由に任意の方法で処理できるように実装が自由にできると思います。
supercat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.