web-dev-qa-db-ja.com

実行されないコードが未定義の動作を呼び出すことはありますか?

未定義の動作(この例では、ゼロによる除算)を呼び出すコードは実行されません。プログラムはまだ未定義の動作ですか?

int main(void)
{
    int i;
    if(0)
    {
        i = 1/0;
    }
    return 0;
}

それはまだ未定義の動作だと思いますが、私をサポートしたり否定したりするための標準に証拠を見つけることができません。

それで、何かアイデアはありますか?

80
Yu Hao

C標準で「動作」と「未定義の動作」という用語がどのように定義されているかを見てみましょう。

参照先は、ISO C 2011標準の N157 ドラフトです。私は、3つの公開されたISO C規格(1990、1999、および2011)の関連する違いを認識していません。

セクション3.4:

動作
外観またはアクション

OK、少しあいまいですが、実際に実行されない限り、特定のステートメントには「外観」がなく、「アクション」もないことに間違いはないと思います。

セクション3.4.3:

未定義の動作
動作は、この国際規格が要件を課さない、移植できないか誤ったプログラム構成または誤ったデータを使用した場合

そのような構成の「使用時」と書かれています。 「使用」という言葉は標準で定義されていないため、一般的な英語の意味に戻ります。実行されない場合、構成は「使用」されません。

その定義の下に注記があります:

注定義されていない可能性のある動作は、予測できない結果で完全に状況を無視することから、(診断メッセージの発行の有無にかかわらず)文書化された方法で環境の特性として文書化された方法で動作すること、変換または実行を終了すること(診断メッセージの発行)。

したがって、コンパイラは、その動作が定義されていない場合、コンパイル時にプログラムを拒否することを許可されます。しかし、それを私の解釈では、プログラムのすべての実行が未定義の動作に遭遇することを証明できる場合にのみを実行できると解釈しています。これは、次のことを意味すると思います。

if (Rand() % 2 == 0) {
    i = i / 0;
}

これは確かにcanの動作が未定義であり、コンパイル時に拒否できません。

実際問題として、プログラムはランタイムテストを実行して、未定義の動作を呼び出さないようにする必要があり、標準はそうすることを許可する必要があります。

あなたの例は:

if (0) {
    i = 1/0;
}

これは決して0による除算を実行しません。非常に一般的なイディオムは次のとおりです。

int x, y;
/* set values for x and y */
if (y != 0) {
    x = x / y;
}

y == 0の場合、除算の動作は未定義ですが、y == 0の場合、除算は実行されません。 potential未定義の動作が実際に発生することはないため、動作は適切に定義されており、同じ理由で例が適切に定義されています。

INT_MIN < -INT_MAX && x == INT_MIN && y == -1でない限り(はい、整数除算はオーバーフローする可能性があります)、それは別の問題です。)

コメント(削除されたため)で、コンパイラがコンパイル時に定数式を評価する可能性があると誰かが指摘しました。これは正しいですが、この場合は関係ありません。

i = 1/0;

1/0は定数式ではありません

constant-expressionは、conditional-expression(割り当てとコンマ式を除く)に減少する構文カテゴリです。プロダクションconstant-expressionは、ケースラベルなどの定数式を実際に必要とするコンテキストで文法onlyに表示されます。だからあなたが書くなら:

switch (...) {
    case 1/0:
    ...
}

次に、1/0は定数式であり、6.6p4の制約に違反する式:「各定数式は、その型の表現可能な値の範囲内の定数に評価される」ため、診断が必要です。しかし、割り当ての右側には、定数式は必要ありません。条件式だけなので、定数式は適用されません。コンパイラーは、コンパイル時に可能な任意の式を評価できますが、動作が実行中に評価された場合と同じである場合(または、if (0)のコンテキストでnot実行中に評価されます()。

定数式のように見えるものは、必ずしもx + y * zのシーケンスと同様に、定数式である必要はありませんx + yは、それが出現するコンテキストのため、additive-expressionではありません。)

それは私が引用しようとしていたN1570セクション6.6の脚注を意味します:

したがって、次の初期化では、
static int i = 2 || 1 / 0;
式は、値が1の有効な整数定数式です。

この質問には実際には関係ありません。

最後に、実行中に何が起こるかではなく、未定義の動作を引き起こすように定義されていることがいくつかあります。 C標準の付録2、セクション2(再び、 N1570ドラフト を参照)は、標準の残りの部分から集められた、未定義の動作を引き起こすものをリストしています。いくつかの例(これが完全なリストであるとは主張していません)は次のとおりです。

  • 空でないソースファイルが、バックスラッシュ文字の直後にない改行文字で終わっていないか、部分的な前処理トークンまたはコメントで終わっています。
  • トークン連結により、ユニバーサル文字名の構文に一致する文字シーケンスが生成されます
  • トークンに変換されない識別子、文字定数、文字列リテラル、ヘッダー名、コメント、または前処理トークンを除いて、ソースファイルで基本ソース文字セットにない文字が検出された
  • 識別子、コメント、文字列リテラル、文字定数、またはヘッダー名に無効なマルチバイト文字が含まれているか、初期シフト状態で開始および終了していません
  • 同じ識別子が同じ翻訳単位内に内部と外部の両方のリンケージを持っている

これらの特定のケースは、コンパイラーが検出できるものです。委員会がすべての実装に同じ振る舞いを課したくなかったか、できなかったため、それらの振る舞いは定義されていないと思います。許可された振る舞いの範囲を定義することは、努力するだけの価値がありませんでした。これらは実際には「実行されないコード」のカテゴリには分類されませんが、ここでは完全を期すために言及します。

71
Keith Thompson

これ article はセクション2.6でこの質問について説明します

_int main(void){
      guard();
      5 / 0;
}
_

著者は、プログラムがguard()が終了しないときに定義されると考えています。彼らはまた、「静的に未定義」と「動的に未定義」の概念を区別することに気づきます。例:

標準の背後にある意図11 それらのコードを生成することが容易でない場合、一般に、状況は静的に未定義にされるように見えます。コードを生成できる場合にのみ、状況を動的に未定義にすることができます。

11)委員会メンバーとの私的な通信。

記事全体を見ることをお勧めします。一緒に取られて、それは一貫した絵を描きます。

記事の作成者が委員会のメンバーと質問について話し合わなければならなかったという事実は、標準があなたの質問に対する回答について現在曖昧であることを確認しています。

31
Pascal Cuoq

この場合、未定義の動作はコードを実行した結果です。したがって、コードが実行されない場合、未定義の動作はありません。

未定義の動作がコードの宣言のみの結果である場合(たとえば、変数のシャドウイングのいくつかのケースが未定義である場合)、非実行コードは未定義の動作を呼び出す可能性があります。

5
Arnaud Le Blanc

未定義の動作については、正式な側面と実際の側面を区別することはしばしば困難です。これは、1989年の標準における未定義の動作の定義です(現在、より新しいバージョンはありませんが、これが大幅に変更されることはないと思います)。

 1つの未定義の動作
動作、移植不能またはエラーのあるプログラム構造、または
の誤ったデータの使用時。この国際標準は要件を課していません
 2注意可能未定義の動作は、予測できない結果を伴う状況の完全な無視
から、環境の特性として文書化された方法での変換またはプログラムの実行時の動作
(
の発行の有無にかかわらず)診断メッセージ)、変換または
の実行を終了します(診断メッセージを発行します)。

正式な観点から言えば、プログラムは未定義の動作を呼び出すと思います。つまり、ゼロによる除算が含まれているという理由だけで、実行時に何が行われるかについては標準が要件を一切設けていません。

一方、実用的な観点からは、直感的に期待どおりに動作しないコンパイラーを見つけて驚かれることでしょう。

2
Nicola Musatti

標準が重大な変更を行い、コードが突然「実行されない」ことがなくなった場合にのみ。しかし、これが「未定義の動作」を引き起こす可能性のある論理的な方法はわかりません。 anythingの原因ではありません。

2
Hrishi

それはまだ未定義の動作だと思いますが、私をサポートしたり否定したりするための標準に証拠を見つけることができません。

プログラムは未定義の動作を起動しないと思います。

不具合レポート#109 は同様の質問に対応し、次のように述べています。

さらに、特定のプログラムのevery実行の可能性が未定義の動作になる場合、その特定のプログラムは厳密に準拠していません。厳密に準拠するプログラムは、そのプログラムの実行によって未定義の動作が発生する可能性があるため、準拠する実装はその変換に失敗してはなりません。 fooは決して呼び出されない可能性があるため、指定された例は、準拠する実装によって正常に変換される必要があります。

2
ouah

私はこの答えの最後の段落に行きます: https://stackoverflow.com/a/18384176/694576

... UBはランタイムの問題であり、コンパイル時の問題ではありません...

したがって、いいえ、呼び出されるUBはありません。

2
alk

標準は、私が正しく覚えているように、それはルールが破られた瞬間から何でもすることが許されていると言います。多分グローバルな味のある特別なイベントがあるかもしれません(しかし私はそのようなことについて聞いたことも読んだこともありません)...それで私は言うでしょう:動作が明確に定義されている限り、これはUBになることはできませんfalseなので、実行時にルールが壊れることはありません。

2
dhein

これは、「未定義の動作」という表現の定義方法、およびステートメントの「未定義の動作」がプログラムの「未定義の動作」と同じかどうかによって異なります。

このプログラムはCのように見えるので、コンパイラーが使用したC標準の詳細な分析(いくつかの回答と同様)が適切です。

特定の標準がない場合、正しい答えは「依存する」です。一部の言語では、コンパイラーの推測によると、最初のエラーの後のコンパイラーは、プログラマーが何を意味するのかを推測し、それでもコードを生成しようとします。他のより純粋な言語では、何かが未定義になると、その未定義がプログラム全体に波及します。

他の言語には「制限付きエラー」という概念があります。一部の限られた種類のエラーについて、これらの言語は、エラーがもたらす可能性のある損害を定義します。暗黙のガベージコレクションを使用する特定の言語では、エラーがタイピングシステムを無効にするかどうかにかかわらず、頻繁に違いが生じます。

0
user3138160