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

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

C/C++のポインタは強力な機能である一方、扱い方を誤ると深刻なバグの原因となります。本記事では、特によく問題になる4種類のポインタ——ダングリングポインタ(ぶら下がりポインタ)voidポインタNULLポインタワイルドポインタ——について、それぞれの特徴と発生原因をコード例とともに解説します。

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

ダングリングポインタとは、すでに解放(削除)されたメモリ領域を指し続けているポインタのことです。無効なメモリを参照するため、プログラムのクラッシュや予期しない動作を引き起こす危険があります。ダングリングポインタが発生する主なケースを以下に紹介します。

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

ローカル変数がstaticでない場合、関数が終了するとその変数は破棄されます。そのため、ローカル変数のアドレスを返すと、ポインタはダングリングポインタになります。

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

このプログラムが返すアドレスは、関数終了後に無効になるため、ガベージ(不定)アドレスとなります。

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

動的に確保したメモリを free() で解放した後も、ポインタは元のアドレスを保持し続けます。この状態がダングリングポインタです。解放後に NULL を代入することで回避できます。

#include <stdlib.h>
#include <stdio.h>

int main() {
    float *p = (float *)malloc(sizeof(float));
    // 動的メモリ割り当て
    free(p);
    // free() 呼び出し後、p はダングリングポインタになる
    p = NULL;
    // NULL を代入すれば、p はダングリングポインタではなくなる
    return 0;
}

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

内側のブロックで宣言された変数は、ブロックを抜けると破棄されます。そのアドレスを保持したポインタは、以降ダングリングポインタとなります。

int main() {
    int *p;
    // 何らかのコード
    {
        int c;
        p = &c;
    }
    // ブロックを抜けたため、ここで p はダングリングポインタ
    // 何らかのコード
    return 0;
}

voidポインタとは

C言語におけるvoidポインタは、特定のデータ型に紐付かないポインタです。メモリ上の任意のデータ位置(変数のアドレス)を指すことができ、汎用ポインタ(general purpose pointer)とも呼ばれます。

一方で、voidポインタには以下の制限があります。

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

サンプルコード

#include <stdlib.h>
#include <stdio.h>

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

    p = &a;
    printf("Integer variable is = %d", *((int*) p));

    p = &b;
    printf("\nFloat variable is = %f", *((float*) p));

    return 0;
}

実行結果

Integer variable is = 7
Float variable is = 7.600000

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

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

NULLポインタとは

NULLポインタとは、何も指していないことを明示するポインタです。主な用途は以下のとおりです。

  • ポインタ変数にまだ有効なメモリアドレスを割り当てていない場合の初期化
  • 有効なアドレスを渡したくない場合に、関数の引数としてNULLを渡す
  • ポインタ変数へアクセスする前のNULLチェックによるエラー処理(NULLでない場合のみ参照解除する、など)

サンプルコード

#include <iostream>
#include <stdio.h>

int main() {
    int *p = NULL; // ポインタをNULLで初期化
    printf("The value of pointer is %u", p);
    return 0;
}

実行結果

The value of pointer is 0.

ワイルドポインタとは

ワイルドポインタとは、初期化されていないために、でたらめなメモリ位置を指しているポインタのことです(NULLですらありません)。どこを指しているかわからないため、参照解除すると未定義動作を引き起こす非常に危険な状態です。

int main() {
    int *ptr;  // ワイルドポインタ(未初期化)
    *ptr = 5;  // 危険!どこに書き込まれるかわからない
    return 0;
}

危険なポインタを防ぐポイント

  • 宣言と同時に初期化する:アドレスが未定なら NULL で初期化し、ワイルドポインタを防ぐ。
  • free() 後は必ず NULL を代入する:ダングリングポインタの発生を防止する。
  • ローカル変数のアドレスを返さない:必要なら static 変数や動的メモリ、呼び出し側で確保したバッファを利用する。
  • 参照解除前にNULLチェックを行う:ポインタ関連のエラー処理を確実に行う。

これらの習慣を身につけることで、ポインタ起因のバグやクラッシュを大幅に減らし、安全で堅牢なC/C++プログラムを書けるようになります。

  1. C/C++のisalpha()とisdigit()関数の使い方を徹底解説

    isalpha()関数とはisalpha()関数は、渡された文字がアルファベット(英字)であるかどうかを判定するための関数です。この関数は「ctype.h」ヘッダーファイルで宣言されており、引数がアルファベットの場合は0以外の整数値を返し、そうでない場合は0を返します。C言語におけるisalpha()の構文は以下の通りです。int isalpha(int value);引数の説明:value - 判定対象となる文字を整数型(int)で渡します。通常はchar型の文字をそのまま指定します。以下はC言語におけるisalpha()の使用例です。サンプルコード#include<stdio.h&g

  2. C/C++のポインタとJavaの参照(リファレンス)の違いを徹底解説

    C/C++とJavaはどちらも広く使われるプログラミング言語ですが、メモリ上のオブジェクトを扱う仕組みが大きく異なります。C/C++では「ポインタ」、Javaでは「参照(リファレンス)」と呼ばれる概念がそれに該当します。本記事では、それぞれの基本的な使い方をコード例とともに確認した上で、両者の主な違いをわかりやすく解説します。 ポインタとは C言語およびC++において、ポインタとは他の変数のアドレス(メモリ上の場所)を格納するための変数です。ポインタを通じて、元の変数の値を間接的に読み書きすることができます。 サンプルコード #include <iostream> using na