未規定動作とは
コンピュータ
プログラミングにおいて、
未規定動作(unspecified behavior)とは、プログラム言語の仕様が動作結果を明確に規定せず、言語処理系(
コンパイラやインタプリタなど)によって異なる可能性のある動作を指します。これは、同じ
ソースコードであっても、異なる
コンパイラでコンパイルした場合や、同じ
コンパイラでも設定を変えた場合に、異なる
実行ファイルが生成され、異なる動作を示す可能性があるということを意味します。
未規定動作の定義
プログラミング言語の標準仕様は、
コンパイラがターゲットプラットフォームに最適化されたコードを生成できるよう、
ソースコードのすべての構造に対する動作を詳細に規定していません。すべてのプログラムに対して正確な動作を規定することは不可能であり、これは言語仕様の欠陥とは見なされません。特にCや
C++では、このような
移植性に影響を与える可能性がある動作を、「処理系定義動作」「未規定動作」「
未定義動作」の3つのカテゴリに分類しています。
未規定動作の正確な定義は、言語によって異なります。
C++では、「適格なプログラム構成や正しいデータに対する、処理系に依存する動作」と定義されており、可能な動作の範囲は規定されるものの、具体的な動作は処理系に委ねられています。Cの標準では、「標準が2つ以上の可能性を示し、どれを選択するかについてそれ以上の制約を課さない動作」と定義しています。
未規定動作は、
未定義動作(undefined behavior)とは異なります。
未定義動作は、プログラムの誤った構造やデータによって引き起こされ、コンパイルや実行における動作の保証が一切ありません。
処理系定義動作との違い
Cおよび
C++では、未規定動作と
処理系定義動作(implementation-defined behavior)を区別します。処理系定義動作では、処理系は特定の動作を選択し、その動作を文書化する必要があります。例えば、
整数型のサイズは処理系定義動作に該当します。選択された動作は、プログラムの実行において文書化された動作と一致している必要があります。
未規定動作の例
部分式の評価順序
多くの
プログラミング言語では、式の中の部分式の評価順序は規定されていません。これは、並列処理などの最適化を可能にするためです。しかし、部分式に副作用がある場合、評価順序によって式の評価結果が異なる可能性があります。
例として、次のコードを考えます。
c
int b = 1;
int f(int x) { b = x + 1; return b;}
int g(int y) { b = y 2; return b;}
int a = f(b) + g(b);
`f`と`g`の両方が`b`を変更する場合、`f(b)`と`g(b)`のどちらが先に評価されるかによって、`a`に格納される結果が変わる可能性があります。
Cおよび
C++では、この問題は関数引数にも適用されます。
c++
include
int f() { std::cout << "f()" << std::endl; return 1; }
int g() { std::cout << "g()" << std::endl; return 2; }
int main() {
int a = f() + g();
return 0;
}
このプログラムは、標準出力に2行の文字列を出力しますが、`f()`と`g()`の評価順序によって表示される順番が異なります。
Javaなどの他の言語では、オペランドや関数引数の評価順序が明示的に定義されています。
未規定動作は、多くの場合、外部から見えるプログラムの動作には影響しませんが、場合によっては結果が異なる可能性があり、それがプログラムの
移植性を損なう原因となることがあります。異なる環境でプログラムを動作させる場合、未規定動作に依存したコードは予期せぬ動作を引き起こす可能性があるため、注意が必要です。
まとめ
未規定動作は、
プログラミング言語の仕様上、動作が明確に定められていない動作であり、処理系によって挙動が異なる可能性があります。これは、
コンパイラの最適化を可能にするために意図的に導入された側面であり、必ずしも問題ではありません。しかし、
移植性を考慮する際には、未規定動作に依存しないコードを記述することが重要です。
参考
移植性
*
未定義動作