1. ホーム
  2. c

[解決済み] 決して実行されないコードは未定義の動作を呼び出すことができるか?

2023-07-03 07:39:52

質問

未定義の動作(この例では0による除算)を呼び出すコードは決して実行されませんが、そのプログラムは未定義の動作でしょうか?

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

私はまだ未定義の動作だと思うのですが、私を支持または否定する証拠が規格に見当たりません。

では、何かアイデアはありませんか?

どのように解決するのですか?

C言語規格では、"behavior" と "undefined behavior" という用語がどのように定義されているかを見てみましょう。

リファレンスは N1570 ISO C 2011 標準のドラフトです。私は、公開されている 3 つの ISO C 標準 (1990、1999、2011) のいずれにおいても、関連する相違点を認識していません。

3.4 節を参照してください。

動作

外見や行動

OK、それは少し曖昧ですが、私は、与えられたステートメントは、それが実際に実行されない限り、"外観"、そして確かに"動作"を持たないことを主張します。

3.4.3 節を参照してください。

未定義の動作

移植不可能な、または誤ったプログラム構成、または誤ったデータを使用した場合の動作。 この国際規格が要求していないもの

"と書かれています。 使用時 と書かれています。use"という単語は規格で定義されていませんので、一般的な英語の意味に立ち戻ります。実行されない場合、構成は使用されません。

その定義の下に注釈があります。

注 定義されていない動作は、状況を完全に無視して予測できない結果をもたらすものから 予測不可能な結果をもたらす完全な無視から、翻訳中やプログラム実行中に またはプログラムの実行中に、その環境に特徴的な文書化された方法で動作する。 環境に特徴的な文書化された方法で翻訳またはプログラム実行中に動作する(診断メッセージの発行の有無にかかわらず)、または 翻訳または実行を終了する(診断メッセージの発行あり)。 診断メッセージの発行あり)。

つまり、コンパイラはあなたのプログラムを拒否することが許されているのです。 コンパイル時に もしその動作が未定義であれば、コンパイラはあなたのプログラムをコンパイル時に拒否することが許されています。しかし、私の解釈では、コンパイラができるのは だけ もしプログラムのすべての実行が未定義の振る舞いに遭遇することを証明できるのであれば。これは暗に、こう言っているのだと思います。

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

というのは、確かに できる が未定義の振る舞いをする場合、コンパイル時に拒否することはできません。

実際問題として、プログラムは未定義の動作の起動を防ぐために実行時テストを実行できなければならず、標準はそれを許可しなければなりません。

あなたの例は

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 . この動作はよく定義されており、あなたの例がよく定義されているのと同じ理由です。 ポテンシャル 未定義の動作が実際に起こることはないからです。

(ただし INT_MIN < -INT_MAX && x == INT_MIN && y == -1 (そう、整数の割り算はオーバーフローすることがあるのです)、でもそれは別の問題です)。

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

i = 1/0;

1/0 は定数表現ではありません .

A 定数式 は構文カテゴリで、以下のように還元されます。 条件式 (に還元される構文カテゴリです(代入とカンマ式は除外されます)。生成される 定数式 は文法の中で のみ は,ケースラベルのような定数表現を実際に必要とする文脈では, と表示されます.ですから、もしあなたが

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

では 1/0 は定数式であり,6.6p4の制約に違反するものである: "各定数式は,型に対して表現可能な値の範囲内にある定数に評価されなければならない。 に違反しているため、診断が必要です。しかし、代入の右辺は、そのような制約を必要としません。 定数式 は、単に 条件式 であるため、定数式の制約が適用されません。コンパイラはコンパイル時に評価できる式は何でも評価できますが、実行時に評価された場合と同じ挙動になる場合のみです(または、の文脈では if (0) , ではなく は実行()中に評価されます。

(にそっくりなもの)。 定数式 は必ずしも 定数式 と同じように x + y * z において、シーケンス x + y 加算式 というのは、それが登場する文脈のためです)。

つまり、私が引用しようとしたN1570の6.6項の脚注のことです。

したがって、次のような初期化では

static int i = 2 || 1 / 0;

という式は、値1を持つ有効な整数定数式です。

は、実はこの質問とは関係ありません。

最後に、未定義の動作を引き起こすと定義されているもので、実行中に起こることに関するものではないものがいくつかあります。C 規格の附属書 J、セクション 2 (ここでも N1570 草案 を参照) は、未定義の動作を引き起こすものを、この標準の残りの部分から集めてリストアップしています。いくつかの例 (これが網羅的なリストであるとは言いません) は以下のとおりです。

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

これらの特殊なケースは、コンパイラが ができることです。 を検出することができます。これらの動作が未定義なのは、委員会がすべての実装に同じ動作を課すことを望まなかったか、課すことができなかったからであり、許容される動作の範囲を定義することは努力に見合わなかったのだと思います。これらは実際には決して実行されないコードのカテゴリに分類されませんが、念のためここで言及します。