識別子が定義されていません - 定義されていない識別子です c++




定義されていない振る舞いを持つ式が実際には実行されないと、プログラムは誤ったものになりますか? (6)

未定義の振る舞い(UB)についての多くの議論では、プログラムにUBを持つ任意の構造のプログラム内に存在するだけで、何もしない(まったく何もしない)実行することが要求されています。 私の質問は、UBがコードの実行に関連付けられている場合であっても、その意味でこのコードを実行すべきかどうかです。ただし、標準で指定された動作(そうでなければ)は、問題のコードを実行すべきではないコンパイル時に決定できない可能性があります)。

より非公式な言い方では、UBの臭いは、プログラム全体が臭いと判断し、その振る舞いが完全に明確なプログラム部分であっても正しく実行することを拒否します。 プログラム例は、

#include <iostream>

int main()
{
    int n = 0;
    if (false)
      n=n++;   // Undefined behaviour if it gets executed, which it doesn't
    std::cout << "Hi there.\n";
}

明確にするために、私はプログラムが整形式であると仮定しています(特に、UBは前処理に関連していません)。 実際、コンパイル時のエンティティではない「評価」に関連するUBに限定して喜んで喜んでいます。 与えられた例に関連する定義は、私が思うに(強調は私のものです):

beforeは、単一のスレッド(1.10)によって実行される評価間の非対称で推移的な対の関係であり、それらの評価の中で部分的な順序を誘導する

オペレータのオペランドの値計算は、オペレータの結果の値計算の前に順序付けられる。 スカラオブジェクトの副作用が、同じスカラオブジェクトの値を使用する値計算または値のどちらに対しても順序付けられていない場合、その動作は未定義です。

最終的な文章の「副作用」と「価値計算」の被験者は、「前に順序付けられた」関係が定義されているため、「評価」のインスタンスであることは暗黙のうちに明白です。

上記のプログラムでは、最終的な文章の条件が満たされている(相互に関連して記載されていない)評価がないこと、およびプログラムにUBがないことを規定しています。 それは間違いではない。

言い換えれば、私のタイトルの質問に対する答えは否定的であると確信しています。 しかし、私はこの問題に関して他の人々の(意欲的な)意見を感謝します。

たぶん、肯定的な答えを提唱する人々のための追加の質問は、間違ったプログラムがコンパイルされたときにあなたのハードドライブの諺の再フォーマットが起こる可能性があるでしょうか?

このサイトのいくつかの関連するポインター:

  • 観察可能な振る舞いと未定義の振る舞い - デストラクタを呼び出さないとどうなりますか?
  • この回答へのコメントhttps://stackoverflow.com/a/24143792/1436796 (私はもはや私自身の答えでは絶対に立っていません)
  • C ++宣言されていない最も初期の動作は何か?
  • 定義されていない行動と邪魔されていない行動の違い、診断メッセージは必要ありません。反対の意見を表す2つの答え

スカラオブジェクトの副作用が他と比較して順序付けされていない場合

副作用は実行環境の状態の変化です(1.9 / 12)。 変更は変更であり、評価された場合、変更を生じる可能性のある表現ではありません。 変更がなければ、副作用はありません。 副作用がない場合、副作用は他のものと比較して順不同ではありません。

これは決して実行されないコードがUBフリーであるということを意味するものではありません(ただし、ほとんどのコードは確実です)。 標準でUBが出現するたびに、個別に検査する必要があります。 (壊れたテキストはおそらく過度に慎重である;下記参照)。

この規格は、

適切に構成されたプログラムを実行する適合する実装は、同じプログラムと同じ入力を持つ抽象機械の対応するインスタンスの可能な実行の1つと同じ観察可能な動作を生成しなければならない。 しかし、そのような実行に未定義の操作が含まれている場合、この国際標準は、その入力でそのプログラム実行している実装については何も要求しない(最初の未定義操作に先行する操作に関してさえも)。

(強調する鉱山)

これは、私が知る限り、「未定義の振る舞い」とは、 プログラム実行中の未定義のオペレーションを意味する唯一の規範的な参照です。 実行なし、UBなし。


ACコンパイラは、プログラムが未定義の動作を未来のある時点で呼び出さないようにするイベントの定義されたシーケンスがない状態に入るとすぐに、それが好きな何かをすることができます。コンパイラが認識する必要がある終了条件を持たず、未定義のビヘイビアを呼び出します)。 そのような場合のコンパイラの振る舞いは、時間と因果関係の法則に拘束されます。 結果が決して使用されない式で未定義の動作が発生する状況では、コンパイラは式のコードを生成しないため(実行しないように)、コンパイラが未定義の動作を使用して他のプログラムの振舞いについての推論。

例えば:

void maybe_launch_missiles(void)
{      
  if (should_launch_missiles())
  {
    arm_missiles();
    if (should_launch_missiles())
      launch_missiles();
  }
  disarm_missiles();
}
int foo(int x)
{
  maybe_launch_missiles();
  return x<<1;
}

Cの現在のC標準では、コンパイラがdisarm_missiles()が終了せずに常に返るが、上記の3つの外部関数が終了する可能性があると判断できる場合は、 foo(-1);文に対して最も効率的な標準準拠の置換が行われfoo(-1); (戻り値は無視される) should_launch_missiles(); arm_missiles(); should_launch_missiles(); launch_missiles(); should_launch_missiles(); arm_missiles(); should_launch_missiles(); launch_missiles();

プログラムビヘイビアは、最初の呼び出しが0以外を返し、 arm_missiles()が返さずに終了した場合、または両方の呼び出しが0以外を返し、 arm_missiles()が返さずに終了した場合に、 should_launch_missiles()呼び出しが終了して終了する場合にのみ定義されます。 これらのケースで正しく動作するプログラムは、それが他の状況で何をしているかにかかわらず、標準に準拠します。 maybe_launch_missiles()から戻ると未定義の動作が発生すると、コンパイラはshould_launch_missiles()への呼び出しがゼロを返す可能性を認識する必要はありません。

結果として、現代のコンパイラの中には、コードとデータ空間を分離し、スタックのオーバーフローをトラップするプラットフォーム上の典型的なC99コンパイラで、何らかの未定義ビヘイビアによって引き起こされる可能性のあるものより悪い可能性があります。 コードが未定義の振る舞いをしていても、無作為な制御の転送を引き起こす可能性があるとしても、少なくとも1回の呼び出しでdisarm_missiles()が呼び出されない限り、 arm_missiles()launch_missiles()を連続して呼び出すことはできません。 should_launch_missiles()はゼロ以外の値を返しました。 しかし、最新のコンパイラは、このような保護を無効にする可能性があります。


いいえ。例:

struct T {
    void f() { }
};
int main() {
    T *t = nullptr;
    if (t) {
        t->f(); // UB if t == nullptr but since the code tested against that
    }
}

もしそうでなければ 、そうするべきです。

ISO Cの定義による動作 (ISO C ++には対応する定義はありませんが、それでもやはり適用可能です)は次のとおりです。

3.4

1の行動

外観または動作

そして、UB:

WG21 / N4527

1.3.25 [defns.undefined]

未定義の動作

[注:この国際標準が動作の明示的定義を省略した場合、またはプログラムが誤った構造または誤ったデータを使用する場合、未定義の動作が予想される可能性があります。 許可された未定義の動作は、状況を完全に予測できない結果で完全に無視することから、環境の文書化された方法で(翻訳メッセージの発行の有無にかかわらず)翻訳またはプログラム実行中に動作すること、翻訳または実行を終了すること(発行診断メッセージの)。 多くの誤ったプログラム構成は未定義の動作を引き起こさない。 彼らは診断される必要があります。 -end note]

上記の「翻訳中に動作する」にもかかわらず、ISO C ++で使用される「動作」という言葉は主にプログラムの実行に関するものです。

WG21 / N4527

1.9プログラムの実行[イントロ。実行]

1この国際標準の意味論的記述は、パラメータ化された非決定論的抽象機械を定義する。 この国際標準は、準拠している実装の構造上の要件を置かない。 特に、抽象機械の構造をコピーしたりエミュレートしたりする必要はありません。 むしろ、以下に説明するように、抽象マシンの観察可能な振る舞いをエミュレートするためには、適合する実装が必要です(注5)。

2抽象機械のある種の側面と操作は、この標準で実装定義(例えば、 sizeof(int) )として記述されている。 これらは抽象機械のパラメータを構成する。 各実装は、これらの点で特性と動作を記述した文書を含めるものとする6)。そのような文書は、その実装に対応する抽象マシンのインスタンスを定義するものとする(以下、対応するインスタンスと呼ぶ)。

3抽象機械の他の側面と操作については、この規格では不特定のものとして記述されている(例えば、割り当て関数がメモリを割り当てられなかった場合(5.3.4) 新しい初期化子での式の評価)。 可能であれば、この国際規格は一連の許容される行動を定義する。 これらは抽象機械の非決定論的側面を定義する。 したがって、抽象機械のインスタンスは、所与のプログラムおよび所与の入力に対して複数の可能な実行を有することができる。

4その他の操作は、この規格では定義されていないものとして記述されている(例えば、 constオブジェクトを変更しようとしたときの効果)。 [注:この規格は、未定義の動作を含むプログラムの動作には何も要求しない。 -end note]

適合する実装は、同じプログラムと同じ入力を持つ抽象機械の対応するインスタンスの可能な実行の1つと同じ観察可能な動作を生成しなければならない。 しかし、そのような実行に未定義の操作が含まれている場合、この国際標準は、その入力でそのプログラムを実行している実装については何も要求しない(最初の未定義操作に先行する操作に関してさえも)。

5)この規定は、要求が遵守されているかのような結果が得られている限り、実装がこの規格の要件を無視することが自由であるため、「現存する(as-if)」ルールと呼ばれることがある。プログラムの観察可能な振る舞い。 たとえば、実際の実装では、その値が使用されておらず、プログラムの観察可能な振る舞いに影響を与える副作用が発生しないと推定できる場合、式の一部を評価する必要はありません。

6)このドキュメントには、条件付きでサポートされている構文とロケール固有の動作も含まれています。 1.4を参照してください。

定義されていない振る舞いは、間違って使用されている特定の言語構成要素によって引き起こされるか、または(標準に準拠しない)移植不可能な方法によって引き起こされることは明らかである。 しかし、この標準では、プログラム内の特定のコード部分がそれを引き起こす原因については何も言及していません。 言い換えれば、 「未定義の振る舞いを持つ」というのは、実行されているプログラム全体のプロパティ(適合するもの)であり、その小さな部分ではありません

C ++コードを対応する振る舞いに正確にマッピングする方法が存在する場合にのみ、標準では、特定のコードが実行されていないと、振る舞いを明確にするための保証が強くなる可能性があります 。 これは、実行に関する詳細な意味モデルがないと(不可能ではないにせよ)難しいです。 要するに、 上記抽象機械モデルによって与えられる操作上の意味論は、より強い保証を達成するのに十分ではない 。 しかし、とにかくISO C ++は決してJVMSやECMA-335ではありません。 そして、言語を記述する正式なセマンティクスの完全なセットがあるとは思っていません。

ここで重要な問題は「実行」の意味です。 「プログラムを実行する」とは、プログラムを実行させることを意味します。 これは事実ではありません。 抽象マシンで実行されるプログラムの表現は指定されていないことに注意してください。 (この規格は、「準拠している実装の構造上の要件を課していない」ということにも注意してください。)ここで実行されるコードは、文字通りC ++のコードでもかまいません(マシンコードでなく、 )。 これにより、コア言語をインタプリタ、オンラインの部分評価者、またはオンザフライでC ++コードを翻訳する他のモンスターとして実装することができます。 その結果、実際には、特定の実装についての知識がなくても、翻訳の段階(ISO C ++ [lex.phases]で定義されている)を実行プロセスより完全に先に分割する方法はありません。 したがって、ポータブルな明確な振る舞いを指定することが難しい場合、翻訳中にUBが発生することを許可する必要があります。

UB自体の(最も重要と思われる)有用性の側面の1つを打ち負かし、悪いコードを許可し、より強力な保証を提供するだけでは不必要です。最終的には無駄になるであろうものを「修正」する努力をしなくても、(不必要に)移植性のないコードを素早く捨てるよう奨励しています。

その他の注意事項:

いくつかの単語は、 このコメントへの私の返信の1つからコピーされ再構築されます。


安全性が重要な組み込みシステムの場合、投稿されたコードは欠陥があるとみなされます。

  1. コードはコードレビューや標準準拠(MISRAなど)に合格すべきではありません。
  2. 静的解析(lint、cppcheckなど)は、これを不具合としてフラグする必要があります
  3. コンパイラの中には、これを警告としてフラグを立てるものもあります(欠陥も同様です)。

プログラムが0(UB)で整数除算を実行するかどうかを決定することは、一般的に停止問題に相当します。 コンパイラがそれを一般的に判断する方法はありません。 したがって、可能なUBの単なる存在は、プログラムの残りの部分に論理的に影響を与えることはできません。標準でその旨が要求されると、各コンパイラベンダはコンパイラで問題解決ソルバを提供する必要があります。

さらに単純で、 次のプログラムは、ユーザーが0を入力した場合にのみUBを持ちます。

#include <iostream>
using namespace std;

auto main() -> int
{
    int x;
    if( cin >> x ) cout << 100/x << endl;
}

このプログラム自体にUBがあることを維持するのは不合理なことです。

しかし、未定義のビヘイビアが発生すると、何かが発生する可能性があります。プログラム内のコードのさらなる実行が損なわれます(スタックが汚染されているなど)。







undefined-behavior