名前空間
変種
操作

未定義動作

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

言語の一定のルールに違反すると、プログラム全体が無意味になります。

目次

[編集] 説明

C++ 標準は、以下の分類のいずれにも当てはならないすべての C++ プログラムの観察可能な動作を、正確に定義します。

  • ill-formed ー プログラムに構文の誤りまたは診断可能な意味論上の誤りがあります。 準拠した C++ コンパイラは、たとえそのようなコードに意味を与える言語拡張 (可変長配列など) を定義していたとしても、診断を発行することが要求されます。 標準の文章はこれらの要件を示すために、すべきですすべきではありません、および ill-formed という言葉を用います。
  • ill-formed (診断は要求されない) ー プログラムに一般的なケースでは診断可能でないかもしれない意味論上の誤り (例えば ODR の違反やリンク時にしか検出できないその他の誤り) があります。 そのようなプログラムが実行された場合、動作は未定義です。
  • 処理系定義の動作 ー プログラムの動作は処理系によって様々であり、準拠した処理系は各動作の効果を文章化しなければなりません。 例えば、 std::size_t の型、1バイトのビット数、 std::bad_alloc::what のテキストなどです。 処理系定義の動作の中にはロケール固有の動作があり、処理系が供給しているロケールに依存します。
  • 未規定の動作 ー プログラムの動作は処理系によって様々であり、準拠した処理系は各動作の効果を文章化することは要求されません。 例えば、評価順序、同一の文字列リテラルが区別可能かどうか、配列確保のオーバーヘッドの量などです。 各未規定の動作の結果は有効な結果の集合のいずれかになります。
  • 未定義の動作 ー プログラムの動作について何の制約もありません。 未定義動作の例は、配列の境界の外側のメモリアクセス、符号付き整数のオーバーフロー、ヌルポインタの逆参照、副作用完了点のないひとつの式内で同じスカラーを2回以上変更する、などです。 コンパイラは未定義動作を診断することは要求されず (単純な状況は診断されることも多いですが)、コンパイルされたプログラムはいかなる意味のある動作を行うことも要求されません。

[編集] 未定義動作と最適化

正しい C++ プログラムは未定義動作を含まないため、実際には未定義動作を持つプログラムを最適化を有効にしてコンパイルするとき、コンパイラは予測できない結果を生むことがあります。

例えば、

[編集] 符号付きのオーバーフロー

int foo(int x) {
    return x+1 > x; // true または符号付きオーバーフローによる未定義動作のいずれか
}

これは以下のようにコンパイルされることがあります (デモ)。

foo(int):
        movl    $1, %eax
        ret


[編集] 境界外のアクセス

int table[4] = {};
bool exists_in_table(int v)
{
    // 最初の4回の繰り返しのいずれかで true を返すか、境界外アクセスによる未定義動作
    for (int i = 0; i <= 4; i++) {
        if (table[i] == v) return true;
    }
    return false;
}

これは以下のようにコンパイルされることがあります (デモ)。

exists_in_table(int):
        movl    $1, %eax
        ret

[編集] 未初期化のスカラー

std::size_t f(int x)
{
    std::size_t a;
    if(x) // x が非ゼロであるか、そうでなければ未定義動作
        a = 42;
    return a; 
}

これは以下のようにコンパイルされることがあります (デモ)。

f(int):
        mov     eax, 42
        ret

以下で示されている出力例は古いバージョンの gcc で観察されたものです。

bool p; // 未初期化のローカル変数
if(p) // 未初期化スカラーへの未定義動作なアクセス
    std::puts("p is true");
if(!p) // 未初期化スカラーへの未定義動作なアクセス
    std::puts("p is false");

出力例:

p is true
p is false

[編集] 無効なスカラー

int f() {
  bool b = true;
  unsigned char* p = reinterpret_cast<unsigned char*>(&b);
  *p = 10;
  // この時点で b からの読み込みは未定義動作
  return b == 0;
}

これは以下のようにコンパイルされることがあります (デモ)。

f():
        movl    $11, %eax
        ret

[編集] ヌルポインタの逆参照

int foo(int* p) {
    int x = *p;
    if(!p) return x; // 上の *p が未定義動作であるか、そうでなければこの分岐に入ることはありません
    else return 0;
}
int bar() {
    int* p = nullptr;
    return *p;        // 無条件の未定義動作
}

これは以下のようにコンパイルされることがあります (gcc を用いた foo と clang を用いた bar)

foo(int*):
        xorl    %eax, %eax
        ret
bar():
        retq

[編集] realloc に渡したポインタへのアクセス

以下に示されている出力例を観察するには clang を選択してください。

#include <iostream>
#include <cstdlib>
int main() {
    int *p = (int*)std::malloc(sizeof(int));
    int *q = (int*)std::realloc(p, sizeof(int));
    *p = 1; // realloc に渡したポインタへの未定義動作なアクセス
    *q = 2;
    if (p == q) // realloc に渡したポインタへの未定義動作なアクセス
        std::cout << *p << *q << '\n';
}

出力例:

12

[編集] 副作用なしの無限ループ

以下に示されている出力例を観察するためには clang を選択してください。

#include <iostream>
 
int fermat() {
  const int MAX = 1000;
  int a=1,b=1,c=1;
  // 副作用なしの無限ループは未定義動作
  while (1) {
    if (((a*a*a) == ((b*b*b)+(c*c*c)))) return 1;
    a++;
    if (a>MAX) { a=1; b++; }
    if (b>MAX) { b=1; c++; }
    if (c>MAX) { c=1;}
  }
  return 0;
}
 
int main() {
  if (fermat())
    std::cout << "Fermat's Last Theorem has been disproved.\n";
  else
    std::cout << "Fermat's Last Theorem has not been disproved.\n";
}

出力例:

Fermat's Last Theorem has been disproved.

[編集] 外部リンク

[編集] 関連項目

未定義動作C言語リファレンス