今回はポインタ変数の理解を深めるための復習と,関連するやや高度な話題について解説する.
ポインタ変数やアドレスの概念はすでに述べたとおりであるが,実際の場面でどのように役立てるかを中心にソースコードを読んでみよう.
ポインタ変数も普通の変数と同様,メモリー上に記憶領域が確保されるので,その場所にはアドレスがあるはずである.
つまり,ポインタ変数の置かれているアドレスを格納するポインタ変数,即ち,ポインタ変数を指すポインタも定義可能である.
ポインタのポインタは,ポインタ変数を関数にアドレス渡しで渡す場合などに用いられる.
さらに,原理的にはポインタのポインタのポインタや,ポインタのポインタのポインタのポインタ...なども定義できるが,あまり使われることはない.
// ポインタのポインタを使用する例 1:
#include <stdio.h>
int main(void)
{
int i = 100;
int *p = &i; // p は i のアドレス
int **pp = &p; // pp は p のアドレス
printf("i = %d, &i = %p \n", i, &i);
printf("*p = %d, p = %p \n", *p, p);
printf("**pp = %d, *pp = %p, pp = %p \n", **pp, *pp, pp);
return 0;
}
実行結果:(アドレスの数値は異なる場合がある)
i = 100, &i = 000000000061FE14
*p = 100, p = 000000000061FE14
**pp = 100, *pp = 000000000061FE14, pp = 000000000061FE08
ポインタが指すものは,普通の変数,配列以外にも,関数・実行コードなどメモリ上に配置できるありとあらゆる物を指すことができる. したがって,データである変数ではなく,実行コード(機械語)である関数を指すポインタも定義可能である. これは関数ポインタとも呼ばれる.
下記のように,関数名はその関数のアドレスを格納しており,その場所を先頭に実行コード(機械語)が書かれていると解釈される.
関数名に()をつけることにより,そのアドレスにある実行コードを処理せよ,という意味になる.
さらに「関数ポインタの配列」も作ることができる.
これは,ソーティングなど同じデータに対して異なるアルゴリズムの関数を実行時に切り替えるなどの処理に重宝する.
#include <stdio.h>
void SayHello(void)
{
printf("Hello!\n");
return;
}
void SayGoodBye(void)
{
printf("GoodBye!\n");
return;
}
int main(void)
{
printf("*** call normal functions ***\n");
SayHello(); // 普通に関数の呼び出し
SayGoodBye(); // 普通に関数の呼び出し
// 関数名に() がつけられていないことに注目!
printf("\naddress of SayHello = %p , SayGoodBye = %p\n\n", SayHello, SayGoodBye );
void (*funcp) (void); // 関数ポインタの定義(難).
// funcpは「引数void, 戻り値void」なる関数へのポインタ
printf("*** call by function pointers ***\n");
funcp = SayHello; // ポインタの指す相手をSayHello関数に設定
(*funcp)(); // 関数ポインタを使った関数の呼び出し
funcp = SayGoodBye; // ポインタの指す相手をSayGoodBye関数に設定
(*funcp)(); // 関数ポインタを使った関数の呼び出し
return 0;
}