エントリポイント
タイトルからも想像出来るでしょうが、ここでは関数を指すポインタについて扱います。
ですが、関数にはアドレスがあるのでしょうか。
関数をコンパイルしたときに、その関数の場所を示すアドレスが割り当てられます。
変数や配列と違って想像し難いですが、関数にもアドレスが存在します。
関数の場所を示すアドレスのことを特別にエントリポイントと呼びます。
例を見てみましょう。
function_pointer_sample1.c
#include <stdio.h> void printHelloWorld() { printf("Hello world!\n"); } int incriment(int a) { return ++a; } void main(void) { void(*fncp)(); fncp = printHelloWorld; printf("%p", fncp); return 0; }
細かい説明は後に回しますが、これを実行するとアドレスらしき数列が表示されます。
これより引数と()を書かない関数名単体は、その関数のアドレスを示していることがわかります。
これは配列と同じですね。
関数の間接参照
それでは関数を指すポインタの定義と、関数ポインタを使った関数の間接参照について説明します。
【ポインタの型】 (*【ポインタ名】)(【引数リスト】)
で関数ポインタを定義します。
これに関数名を代入することでアドレスが格納されます。
【ポインタ名】(【引数リスト】)
で、通常の関数と同様に呼び出すことができます。
このとき呼び出される関数は、代入したアドレスを持つ関数です。
この記法ですが、通常の関数と混同してしまうため、あまり好ましくありません。
そのため
(*【ポインタ名】)(【引数リスト】)
と記述するのが良いでしょう。
実行結果は同じになります。
ポインタ名に*をつけると、そのポインタの指す値を示すことになるので、この場合はポインタの示す関数が実行されます。
*ポインタ名に括弧をつけているのは、演算子の優先順位の問題です。
関数ポインタの定義と呼び出し時に引数を指定することで、勿論引数付きの関数を呼び出せます。
ですが、指定した引数と返り値を持つ関数でないと代入できません。
サンプルコード
function_pointer_sample2.c
#include <stdio.h> void printHelloWorld() { printf("Hello world!\n"); } int incriment(int a) { return ++a; } void main(void) { void(*fncp1)(); int(*fncp2)(int); fncp1 = printHelloWorld; fncp2 = incriment; (*fncp1)(); printf("%d\n", (*fncp2)(3)); return 0; }
応用例
この構文の素晴らしい点は、間接参照できるというところです。
すなわち、格納するアドレスを変えることで実行毎に呼び出す関数を変えることができます。
例えば、関数ポインタの配列を定義することで、要素数を変えるだけで好きな関数を呼び出せます。
他にも、関数ポインタを引数にすることで、指定した関数を別の関数内で呼び出すことが出来ます。
これをもとに、有効な使い方を考えてみると良いでしょう。