サンプルプログラム ptr3.c
(13-2)ポインタのポインタ(ポインタへのポインタ)
ポインタのポインタ(ポインタへのポインタ)と言ってもポインタには違いありません。
「ポインタが指し示すアドレス値のデータがポインタです」と言っているだけです。
ポインタのポインタの宣言の仕方
ポインタのポインタを宣言するには変数名の前に**(アスタリスク)を2つ付けます。
int **pInt; // int型のポインタのポインタ char **pChar; // char型 〃 void **pVoid; // void型 〃 float **pFloat; // float型 〃 double **pDouble; // double型 〃
ポインタのポインタの宣言に型を指定するのは、
参照するメモリの内容はポインタ(アドレス値)なのですが更にそのポインタが指し示すアドレス値の内容が何であるかを指定するためです。
つまり、int型のデータが書き込まれたメモリを参照するときはintで宣言し、
float型のデータが書き込まれたメモリを参照するときはfloatで宣言します。
ポインタのポインタの使い方としては大きく分けて2つになるかと思います。
1つはポインタ配列にアクセスするため。
もう1つはハンドル的な使い方です。
ポインタ配列にアクセスする
#include <stdio.h> void main( void ) { int i; int iArr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int iArr2[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; int iArr3[] = { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; int *piArr[] = { iArr1, iArr2, iArr3, NULL }; double dArr1[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 }; double dArr2[] = { 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0 }; double dArr3[] = { 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0 }; double *pdArr[] = { dArr1, dArr2, dArr3, NULL }; int *pi, **ppInt; double *pd, **ppDouble; ppInt = piArr; // 配列変数名はアドレス値(固定値)を持っていますのでポインタに代入できます! ppDouble = pdArr; // 〃 for(; *ppInt && *ppDouble; ) { pi = *ppInt++; pd = *ppDouble++; for( i = 0; i < 10; i++ ) { printf( "i = %d, *pi = %d, *pd = %2.1f\n", i, *pi, *pd ); pi++; pd++; } } }
プログラムの実行結果は以下のようになります。
i = 0, *pi = 1, *pd = 1.0 i = 1, *pi = 2, *pd = 2.0 i = 2, *pi = 3, *pd = 3.0 i = 3, *pi = 4, *pd = 4.0 i = 4, *pi = 5, *pd = 5.0 i = 5, *pi = 6, *pd = 6.0 i = 6, *pi = 7, *pd = 7.0 i = 7, *pi = 8, *pd = 8.0 i = 8, *pi = 9, *pd = 9.0 i = 9, *pi = 10, *pd = 10.0 i = 0, *pi = 11, *pd = 11.0 i = 1, *pi = 12, *pd = 12.0 i = 2, *pi = 13, *pd = 13.0 i = 3, *pi = 14, *pd = 14.0 i = 4, *pi = 15, *pd = 15.0 i = 5, *pi = 16, *pd = 16.0 i = 6, *pi = 17, *pd = 17.0 i = 7, *pi = 18, *pd = 18.0 i = 8, *pi = 19, *pd = 19.0 i = 9, *pi = 20, *pd = 20.0 i = 0, *pi = 21, *pd = 21.0 i = 1, *pi = 22, *pd = 22.0 i = 2, *pi = 23, *pd = 23.0 i = 3, *pi = 24, *pd = 24.0 i = 4, *pi = 25, *pd = 25.0 i = 5, *pi = 26, *pd = 26.0 i = 6, *pi = 27, *pd = 27.0 i = 7, *pi = 28, *pd = 28.0 i = 8, *pi = 29, *pd = 29.0 i = 9, *pi = 30, *pd = 30.0
iArr1~iArr3、dArr1~dArr3はそれぞれint型とdouble型の配列です。
piArrとpdArrはポインタ配列です。
ポインタ配列はポインタのポインタと似ていますがアドレス値が固定(配列なので)であるところが異なります。
ポインタのポインタはppIntとppDoubleです。
ppInt = piArr; // 配列変数名はアドレス値(固定値)を持っていますのでポインタに代入できます! ppDouble = pdArr; // 〃
の行でポインタ配列のアドレス値をポインタのポインタに代入しています。
pi = *ppInt++; pd = *ppDouble++;
の行でそれぞれのアドレス値をポインタに代入してiArr1~3、dArr1~3の値にアクセスしています。
で、これがなんの役に立つの?と思われるかもしれません。
まぁ私的にはポインタ配列としてそのまま書くよりも「プログラムがすっきりと書ける」とか「より高速なプログラムが書ける」と言ったところだと思います。
それと、プログラム中にNULLというキーワードが出てきましたがこれはアドレス値が0(Windowsパソコンの場合)であることを表しています。
ポインタが保持しているアドレス値がNULLの場合、そのデータを参照しようとするとアドレスエラーになり実行途中でも停止してしまうことがあります。
C言語でプログラムを開発しているとよくお目にかかると思いますので覚えておきましょう。
ハンドルとして使用する
Windowsでプログラムを開発するようになりますとハンドルというものがよく出てくるようになります。
ではハンドルとは何かと申しますと、ポインタのポインタですという事になります。
例えばGlobalAlloc APIでメモリを確保しようとしたとき、戻り値をハンドルとした場合には当然ですがハンドルが返ってきます。
ハンドルで返すメリットは何かといいますと、「OSが何かの都合でメモリの位置を変えたい」といったときに変更できることです。
メモリの位置を変更(移動)した場合、ハンドルの中身を移動先のメモリアドレスに変更するだけでアプリケーション側からは特に(プログラム的に)問題なく変更できることになります。
その代わりにアプリケーションは確保したメモリを参照する時にはメモリをロック(OSに移動されないように)してから参照するようにしなければなりません。
またメモリを参照し終えたら(OSが移動してもいいように)メモリをアンロックする必要があります。
アプリケーション側からすればひと手間増えるので面倒に感じるかもしれませんが、有限なメモリを効率的に使用できるようになりますので我慢しましょう。