【6日でできるC言語入門】ポインタのポインタ(ダブルポインタ)

 C言語の学習が進むと、ポインタの使い方に加えて「ポインタのポインタ(二重ポインタ/ダブルポインタ)」という概念に出会います。ダブルポインタは一見複雑に見えますが、ポインタの本質を理解する上でとても重要なテーマです。
 ダブルポインタは「ポインタを指し示すためのポインタ」です。多次元配列の操作や関数での配列の受け渡し、メモリ管理の高度な操作など、実践的なプログラミングでよく利用されます。
 ここでは、ダブルポインタの基本概念から具体的な使い方まで、図やサンプルコードを用いて詳しく解説します。

1.ダブルポインタの基本

1.1. ダブルポインタとは

通常のポインタは「変数のアドレス」を記憶しますが、ダブルポインタ(二重ポインタ)は「ポインタ変数のアドレス」を記憶するポインタです。

用語説明
変数データそのものを記憶
ポインタ変数のアドレスを記憶
ダブルポインタポインタ変数のアドレス(=ポインタのポインタ)

宣言例

int a = 10;     // 普通の変数
int* p = &a;    // aのアドレスを保持するポインタ
int** pp = &p;  // pのアドレスを保持するダブルポインタ

1.2. メモリ上の関係

以下の図は、appp の関係を表しています。

意味
a変数aの値10
&aaのアドレス0x7ffd8a..
paのアドレス0x7ffd8a..
*paの値10
&ppのアドレス0x7ffd8b..
pppのアドレス0x7ffd8b..
*pppの値(aのアドレス)0x7ffd8a..
**pp*pの値(aの値)10

2.ダブルポインタの使い方

2.1. 基本例:アドレスの追跡

サンプル:アドレスの追跡

プロジェクト/ファイル名: Lesson61_1/main.c

#include <stdio.h>

int main(void) {
    int number = 42;        // 普通の整数変数
    int* ptr = &number;     // numberのアドレスを保持
    int** dptr = &ptr;      // ptrのアドレスを保持

    printf("numberの値:%d\n", number);
    printf("numberのアドレス:%p\n", (void*)&number);
    printf("ptrが指す値:%d\n", *ptr);
    printf("ptrの値(numberのアドレス):%p\n", (void*)ptr);
    printf("dptrが指す値(ptrの値):%p\n", (void*)*dptr);
    printf("dptrが指す値が指す値(numberの値):%d\n", **dptr);

    return 0;
}

実行結果例(アドレスは環境により異なります)

numberの値:42
numberのアドレス:000000D1040FFB54
ptrが指す値:42
ptrの値(numberのアドレス):000000D1040FFB54
dptrが指す値(ptrの値):000000D1040FFB54
dptrが指す値が指す値(numberの値):42

解説

  • ptrnumberのアドレスを持つ
  • dptrptrのアドレスを持つ
  • *dptrptrの値(=numberのアドレス)
  • **dptrは実際のデータ(numberの値)

2.2. 配列や文字列の配列への応用

ダブルポインタは「文字列の配列」や「多次元配列」でよく使われます。

サンプル:複数の文字列を関数に渡す

プロジェクト/ファイル名: Lesson61_2/main.c

#include <stdio.h>

// 文字列配列を引数として受け取る関数
void show_names(char** names, int count) {
    int i;
    printf("受け取った名前一覧:\n");
    for (i = 0; i < count; i++) {
        printf("%s\n", names[i]);
    }
}

int main(void) {
    char* list[] = { "リンゴ", "バナナ", "メロン" };
    show_names(list, 3);
    return 0;
}

実行結果

受け取った名前一覧:
リンゴ
バナナ
メロン

解説

  • char* list[]文字列(char型配列)へのポインタの配列
  • listを関数に渡す時はchar**として渡せる
  • 関数側では二重ポインタでアクセス

3.ダブルポインタの応用と注意点

3.1. メモリ動的確保・関数でのポインタ操作

ダブルポインタは、関数の引数としてポインタの値自体を変更したい場合にも利用されます。

サンプル:関数内でポインタ自体を変更

プロジェクト/ファイル名: Lesson61_3/main.c

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

void create_array(int** p_arr, int size) {
    *p_arr = (int*)malloc(sizeof(int) * size);
    if (*p_arr == NULL) {
        printf("メモリの確保に失敗しました。\n");
    }
}

int main(void) {
    int* data = NULL;
    create_array(&data, 4);
    if (data != NULL) {
        for (int i = 0; i < 4; i++) {
            data[i] = i + 1;
        }
        printf("作成した配列:");
        for (int i = 0; i < 4; i++) {
            printf("%d ", data[i]);
        }
        printf("\n");
        free(data);
    }
    return 0;
}

実行結果

作成した配列:1 2 3 4

解説

  • create_arrayは「ポインタのアドレス」を受け取るので二重ポインタ
  • 関数内で*p_arr = ...と書くことで呼び出し元のポインタ変数に確保したアドレスをセットできる。

まとめ

  • ダブルポインタは「ポインタのアドレス」を保持するためのポインタ
  • 多次元配列や文字列配列、動的メモリ確保や関数の引数でよく使われる。
  • 二重ポインタでデータの間接参照が可能
  • 応用力を高めるために、「なぜ二重ポインタが必要なのか」を意識しながら使い方をマスターしましょう。

ダブルポインタを使いこなすことで、C言語のデータ構造や高度なメモリ操作が自在にできるようになります。