検討する
void swap(int* a, int* b)
{
if (a != b){
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
}
int main()
{
int a = 0;
int b = 1;
swap(&a, &b); // after this b is 0 and a is 1
return a > b ? 0 : a / b;
}
swap
は、コンパイラをだましてプログラムを最適化しないようにする試みです。
このプログラムの動作は定義されていますか? a / b
に到達することはできませんが、到達できる場合は、ゼロによる除算が得られます。
この質問は、特定のコード構造やプラクティスの有用性、またはC++について書かれたものに基づいて、その標準であろうと別のSO回答であろうと、関係なく)に基づく必要はありません。 C++の定義がどれほど似ているか。考慮すべき重要なことは Cの未定義の振る舞いの定義 :
動作、移植不可能または誤ったプログラム構成または誤ったデータの使用時、この国際規格は要件を課していません
(C2011、3.4.3/1;強調を追加)
したがって、未定義の動作は、単なる存在によってではなく、一時的に(構成またはデータの「使用時に」)トリガーされます。* これは、データから生じる未定義の動作とプログラム構造から生じる未定義の動作に対して一貫していると便利です。標準はそこで一貫している必要はありませんでした。また、別の回答で説明されているように、この「使用時」の定義は、プログラムが誤ったデータに関連する未定義の動作を実行することを回避できるため、優れた設計上の選択です。
一方、プログラムが未定義の動作を実行する場合、標準の定義から、プログラムの動作全体が未定義であることがわかります。この結果として生じる不定義は、誤ったデータまたは構成に直接関連付けられたUBが、原則として、プログラムの他の部分の動作を遡及的に(または明らかにそう)変更することを含む可能性があるという事実から生じるより一般的な種類です。もちろん、起こり得ることには言語外の制限があります-いいえ、鼻の悪魔は実際には何も現れません-しかし、それらは必ずしも人が想像するほど強力ではありません。
* 警告:一部のプログラム構造は、翻訳時に使用されます。これらはプログラム変換でUBを生成し、その結果、プログラムのすべての実行は完全に未定義の動作をします。ややばかげた例として、プログラムソースがエスケープされていない改行で終わっていない場合、プログラムの動作は完全に定義されていません( C2011、5.1.1.2/1 、ポイント2を参照)。
評価されない式の動作は、プログラムの動作とは無関係です。式が評価された場合に定義されない動作は、プログラムの動作とは関係ありません。
もしそうなら、このコードは役に立たないでしょう:
if (p != NULL)
…; // Use pointer p.
(XORはトラップ表現を生成する可能性があるため、未定義の動作をする可能性があります。オブジェクトを揮発性として宣言することで、このような学術例の最適化を無効にすることができます。オブジェクトが揮発性の場合、C実装はその値が変更される可能性があるかどうかを知ることができません。したがって、オブジェクトを使用するたびに、実装はその値を読み取る必要があります。)
一般に、実行された場合に未定義の振る舞いを呼び出すコードは、実行されていない場合は効果がないはずです。ただし、実際の実装が逆の動作をし、制約違反ではないものの、定義された動作では実行できないコードの生成を拒否する場合がいくつかあります。
extern struct foo z;
int main(int argc, char **argv)
{
if (argc > 2) z;
return 0;
}
私が標準を読んだことにより、不完全な型での左辺値変換を未定義の動作を呼び出すものとして明示的に特徴付けているため(特に、実装がそのようなもののコードを生成できるかどうかは不明です)、argc
が次の場合、標準は動作に要件を課しません。 3つ以上。上記のコードが違反するという標準の制約を特定することはできませんが、argc
が2以下の場合、動作を完全に定義する必要はありません。それにもかかわらず、gccやclangを含む多くのコンパイラーは、上記のコードを完全に拒否します。