ユーニックス総合研究所

  • home
  • archives
  • c-double-pointer

C言語のポインタのポインタを解説

  • 作成日: 2021-04-26
  • 更新日: 2023-12-26
  • カテゴリ: C言語

C言語のポインタのポインタ

C言語ではポインタ変数のアドレスを別のポインタ変数に保存することができます。
このとき、保存先のポインタ変数をポインタのポインタと言います。

ポインタのポインタを使うとポインタ変数を間接的に参照することが可能になり、複雑なプログラムを作ることもできるようになります。
しかしポインタのポインタは「わかりづらい」として、あまり使わないという人もいます。
ですが知識としてポインタのポインタを知っておくことは、開発者の選択肢を増やします。

この記事ではポインタのポインタについて具体的に↓を見ていきます。

  • ポインタのおさらい
  • ポインタのポインタ
  • ポインタのポインタのポインタ

ポインタのおさらい

簡単にポインタのおさらいをしたいと思います。
たとえばint型のポインタ変数を宣言するときは↓のようにしました。

int *p;  // int型のポインタ変数  

このポインタ変数に、int型変数numのアドレスを格納するときは↓のようにします。

int num = 10;  // int型の整数を保存した変数  
int *p = #  // numのアドレスをpに保存  

↑のようにすると、ポインタ変数pには変数numのアドレスが保存され、pから間接的にnumを参照することが出来るようになります。
ポインタ変数pから変数numの値を書き換えたい時は、

*p = 20;  // ポインタを実体化して変数numの値を書き換え  

のように変数の頭にアスタリスク(*)をつけて実体化し、その実体に上書きしたい値を代入します。
↑のようにすると変数numの値は20に書き変わります。

このようなポインタを使った参照のことを間接参照と言います。
ポインタを使って間接的に参照するので間接参照と言うことです。
この間接参照は多重化することが可能です。
この多重化した参照の1例が今回のポインタのポインタです。

関連記事: C言語のポインタのメリットとは?コピーしますかメモリを共有しますか

ポインタのポインタ

それではポインタのポインタをやってみましょう。
ポインタのポインタ変数を宣言するにはアスタリスクを2つつけます。

int **pp;  // ポインタのポインタ変数  

この時、アスタリスク1つを「ポインタ」と読み替えても良いと思います。
アスタリスクが2つ付いてるので「ポインタのポインタ」、つまり3つ付いてれば「ポインタのポインタのポインタ」ということになります。

さらにポインタ変数を1つ用意します。
このポインタ変数pはポインタのポインタppにそのアドレスを保存するポインタです。

int *p;  // ポインタ変数  
int **pp;  // ポインタのポインタ変数  

さらにint型の変数numを用意します。

int num = 10;  // int型の変数num  
int *p;  // ポインタ変数  
int **pp;  // ポインタのポインタ変数  

ここからポインタのポインタ変数から変数numの値を参照する方法を解説していきます。
まず変数numのアドレスをpに保存します。

int num = 10;  
int *p = #  // ポインタ変数pに変数numのアドレスを保存  
int **pp;  

ここまでは普通のポインタです。
さらにポインタ変数pのアドレスをポインタのポインタ変数ppに保存します。

int num = 10;  
int *p = #  
int **pp = &p;  // ポインタ変数pのアドレスをppに保存  

これでポインタのポインタppを使う準備が整いました。
ポインタのポインタから値を参照するには、普通のポインタと同じくアスタリスク(*)で参照します。
ポインタのポインタではアスタリスク1つで保存されたポインタを参照し、アスタリスク2つで保存されたポインタを実体化します。

#include <stdio.h>  

int main(void) {  
    int num = 10;  
    int *p = &num;  
    int **pp = &p;  

    printf("p = %p\n", p);  // ポインタ変数pに保存されている値を出力  
    printf("*pp = %p\n", *pp);  // ポインタのポインタppを関節参照して値を出力  
    printf("**pp = %d\n", **pp);  // ポインタのポインタppを多重間接参照して値を出力  

    return 0;  
}  

↑のコードをコンパイルして実行すると↓のような結果になります。

p = 0x7ffc34b9df24  
*pp = 0x7ffc34b9df24  
**pp = 10  

*「0x7ffc34b9df24」というアドレス値は環境によって変わります。
ポインタのポインタppをアスタリスク1つで参照すると、ポインタ変数pの値、つまり変数numのアドレス値を出力しているのがわかります。
そしてppをアスタリスク2つで多重間接参照すると変数numの値を参照できているのがわかります。

ポインタのポインタのポインタ

ポインタのポインタのポインタ、つまり3重の間接参照は基本的にはポインタのポインタの応用です。
原理的にはポインタのポインタと同じです。

ポインタのポインタのポインタは果たして使われるのか? というところですが、私はあまり使ったことがありません。
使うとしてもごくごくたま~にという感じです。
ほとんどの場合、ポインタのポインタで用が足ります。

ポインタのポインタのポインタは↓のように書きます。

#include <stdio.h>  

int main(void) {  
    int num = 10;  
    int *p = &num;  
    int **pp = &p;  
    int ***ppp = &pp;  // ポインタのポインタのポインタにポインタのポインタ変数のアドレスを代入  

    printf("p = %p\n", p);  // ポインタ変数pに保存されている値を出力  
    printf("*ppp = %p\n", *ppp);  // pppを関節参照して値を出力  
    printf("**ppp = %p\n", **ppp);  // pppを2重間接参照して値を出力  
    printf("***ppp = %d\n", ***ppp);  // pppを3重間接参照して値を出力  

    return 0;  
}  

↑のコードを実行すると↓のような結果になります。

p = 0x7ffe96da1554  
*ppp = 0x7ffe96da1548  
**ppp = 0x7ffe96da1554  
***ppp = 10  

ポインタのポインタのポインタを使っても変数numの値を最終的に参照できているのがわかります。
このように多重間接参照はアスタリスクの数を増やせば何重にもすることができます。
もちろんコンパイラによってその数には制限があると思いますが。

ただご覧のようにぱっと見であまりわかりやすいコードとは言えません。
ですので、乱用は避けたほうが無難かと思われます。
もっとも、個人開発でチームとして開発することが無い場合は使っても良いかもしれません。
ですがその場合も将来の自分が「なんだこのコードは」となる可能性がありますね。

🦝 < 個人開発あるある

おわりに

今回はポインタのポインタを解説しました。
多重間接参照は知っておくと便利でなので、ぜひこの記事をブックマークしてみてください。

🦝 < 多重請負!

🐭 < やめれ