C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++のダングリングポインタ・voidポインタ・NULLポインタ・ワイルドポインタの違いを徹底解説

はじめに

C++では、ポインタの扱い方を誤ると予期しない動作やクラッシュの原因となります。本記事では、特に注意が必要な4種類のポインタ——「ダングリングポインタ」「voidポインタ」「NULLポインタ」「ワイルドポインタ」——について、それぞれの特徴と具体例を交えて詳しく解説します。

ダングリングポインタ(ぶら下がりポインタ)

ダングリングポインタとは、すでに解放(削除)されたメモリ領域を指し続けているポインタのことです。以下のような状況で発生します。

1. 関数呼び出しによる発生

ローカル変数がstaticで宣言されていない場合、関数の終了とともにその変数は破棄されるため、ローカル変数へのポインタは無効なもの(ダングリングポインタ)になります。

int *show(void) {
    int n = 76;
    /* ... */
    return &n;
}

このプログラムが返すアドレスは、すでに存在しない変数を指しているため、出力結果は不定(ガベージ)アドレスとなります。

2. メモリ解放による発生

free()などで動的に確保したメモリを解放した後も、ポインタ変数自体は同じアドレスを保持し続けます。これがダングリングポインタの典型的な例です。

int main() {
    float *p = (float *)malloc(sizeof(float));
    // 動的メモリ確保

    free(p);
    // free() 呼び出し後、p はダングリングポインタになる

    p = NULL;
    // NULL を代入すれば、もはやダングリングポインタではなくなる
    return 0;
}

このように、メモリ解放後すぐにポインタへNULLを代入しておくことは、安全性を高める基本的な作法です。

3. 変数がスコープ外に出ることによる発生

int main() {
    int *p;
    // 何らかのコード
    {
        int c;
        p = &c;
    }
    // ブロックを抜けた時点で c は破棄される
    // ここで p はダングリングポインタになっている
    return 0;
}

内側のブロックで宣言された変数cは、ブロックを抜けると無効になるため、そのアドレスを保持したpは危険な状態になります。

voidポインタ

voidポインタは、特定のデータ型と関連付けられていないポインタです。あらゆる型の変数アドレスを格納できるため、「汎用ポインタ」とも呼ばれます。ただし、以下のような制限があります。

  • サイズが確定していないため、ポインタ演算が行えない
  • 適切な型にキャストしない限り、参照外し(デリファレンス)できない

使用例

#include <iostream>
using namespace std;

int main() {
    int a = 7;
    float b = 7.6;
    void *p;

    p = &a;
    cout << *((int*) p) << endl;   // int型へキャストして参照外し

    p = &b;
    cout << *((float*) p);         // float型へキャストして参照外し

    return 0;
}

実行結果

7
7.6

処理の流れ(アルゴリズム)

  1. 変数aを整数値で、変数bを浮動小数点値で初期化します。
  2. voidポインタpを宣言します。
  3. (int*)pのようにキャストすることで、int型として値を取り出せます。
  4. p = &bとすると、同じvoidポインタがfloat型変数を指すようになります。
  5. (float*)pでキャストし、値を出力します。

このように、voidポインタは一時的にどんな型のアドレスでも受け止められる柔軟性がありますが、利用時には必ず正しい型へのキャストが必要です。

NULLポインタ

NULLポインタは、「何も指していない」ことを明示的に表すポインタです。主な用途は以下の3つです。

  • 初期化: ポインタ変数にまだ有効なメモリアドレスを割り当てていない段階で、NULLで初期化しておく。
  • 引数としての受け渡し: 有効なアドレスを渡す必要がない場合、関数の引数にNULLポインタを渡す。
  • エラー処理: ポインタへアクセスする前にNULLチェックを行い、NULLでない場合のみ参照外しをする。これにより、ポインタ関連のコードで安全なエラー処理が可能になる。

使用例

#include <iostream>
using namespace std;

int main() {
    int *p = NULL;  // ポインタをNULLで初期化

    cout << "The value of pointer is ";
    cout << p;

    return 0;
}

実行結果

The value of pointer is 0.

C++11以降では、NULLの代わりにnullptrを使うことが推奨されています。nullptrは型安全であり、意図しない暗黙の型変換を防げます。

ワイルドポインタ

ワイルドポインタとは、初期化されておらず、不定の任意のメモリ位置を指しているポインタのことです。NULLすら指していないため、参照外しすると未定義動作を引き起こし、非常に危険です。

int main() {
    int *ptr;    // 初期化されていない → ワイルドポインタ
    *ptr = 5;    // 未定義動作!どこに書き込まれるか不明
    return 0;
}

対策としては、ポインタを宣言したら必ずnullptrまたは有効なアドレスで初期化することが重要です。

まとめ:4種類のポインタの比較

種類特徴危険度/注意点
ダングリングポインタ解放済み・スコープ外のメモリを指している高:free後はNULLを代入する
voidポインタ型を持たない汎用ポインタ中:演算不可・キャスト必須
NULLポインタ何も指していないことを明示低:安全な初期化・判定に活用
ワイルドポインタ未初期化で不定のメモリを指す最高:必ず初期化してから使う

ポインタはC++の強力な機能である反面、扱いを誤ると深刻なバグにつながります。「使う前に初期化する」「解放後にNULLを入れる」「参照外し前にNULLチェックをする」——この3つの習慣を守ることで、多くのポインタ起因のトラブルを防ぐことができます。

  1. C++で二分木の各ノードに次の右ポインタを設定する(Part II)

    問題概要 次のような二分木を考えてみましょう。各ノードは (data, left, right, next) というフィールドを持っており、left は左部分木を、right は右部分木を指します。そして next ポインタは、同じ階層における一つ右隣のノードを指すものです。右隣にノードが存在しない場合は null になります。初期状態ではすべての next ポインタが null に設定されているため、これらのリンクを適切に張る必要があります。 例えば、下図のような木が与えられた場合、次のように変換されます。 解法のアプローチ この問題は、next ポインタそのものを利用して各レベルを

  2. C++で完全二分木の各ノードにnextポインタ(次の右ポインタ)を設定する方法

    問題概要完全二分木を考えます。各ノードは (data, left, right, next) という4つのフィールドを持っており、left は左部分木を、right は右部分木を指します。next ポインタは、同じレベル(階層)における「次のノード」を指すためのもので、右隣にノードが存在しない場合は null となります。初期状態ではすべての next ポインタが null に設定されているため、これらのリンクを適切に張り直すことが本記事の目的です。例えば、以下のような木がある場合、これを次のように変換します。解法のアプローチこの問題は、レベルごとにノードをたどりながら next ポインタを順