C 関数ポインタ

提供:yonewiki

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC 関数ポインタになっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

関数ポインタ

関数っていうのは、スタートアップ関数であるmain関数やWindowsであればwmain(_tmain)関数、あるいはwWinMain関数も含めて、すべて関数であると言えます。VisualStudioであればプロジェクトのプロパティでリンカのオプションとして指定する詳細設定の中のエントリポイントの設定によって最初に実行される関数を指定するようになっていて、最初に実行される関数を含めて、すべては関数やクラスの中の関数を呼び出すことによってプログラムが実行されます。Windowsのようなユーザインターフェースを持つプログラムの場合は大抵、割り込み処理を基にした概念が使われ、プログラムは動きっぱなしで、最初の起動時に呼ばれる関数さえ決まっていれば、一連の起動時にだけ実行する処理を実行し、何もしない待機状態になったり、特定の信号によってOSが割り込みを感知するとプログラムに割り込み処理が発生したことを通知し、通知されたイベントに対する関数が動作したりするような動作になります。あるいは特定の処理や特定の割り込み通知によって呼び出された関数によって、プログラムを終了することになります。このような関数と呼ばれているもの自体もメモリのどこかに配置されてプログラムは処理されることから、関数自体もアドレスを持ちます。呼び出し規約というものにしたがって、一定の法則に従って関数はメモリ上にスタックした関数パラメータを読み込んだり、ポップしたりしながら処理を進めます。関数とはそういうものだというイメージをもっていただければ、この項目は理解しやすくなると思います。具体的な個々の関数呼び出し規約の仕組みやスタートアップ関数の決定方法については別の記事で記述したいと思いますが、どこまでわかりやすく説明できるかは未知数です。関数は外部(OSあるいはタイプライブラリあるいは外部機器信号)から呼び出されたり、内部から呼び出されたり、内部から外部を呼び出したりするのが一般的です。そこにはセキュリティのような概念も登場します。


まずは、関数ポインタによって関数にアドレスが付与される簡単な仕組みを確認してみましょう。以下のようなプログラムで関数はアドレスを保持していることが簡単に確認できます。

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
  printf("%08x",_tmain);

  retrun 0;
}

処理結果

0d31028
 

上記のような結果から_tmainという名前の関数もアドレスが保持されていて、アドレス演算子を付与しなくても、出力引数として関数名を与えると、ポインタ変数と同じようアドレスが出力され、ポインタと同様に扱えることがわかります。


となると、関数を扱うポインタ変数の作り方、定義する方法が必要になるわけで、以下のようにして関数を扱うポインタ(関数ポインタ)変数が宣言できます。


戻り値の型名 (*関数ポインタ変数)(引数の型名)


具体的には、以下のような記述になります。

#include "stdafx.h"

void example_function1(int n,int m){
    printf("example_function1 %d %d\n",n, m);  
};

int _tmain(int argc, _TCHAR* argv[])
{
  void (*pfuncExample1)(int,int);//定義
  pfuncExample1 = example_function1;//代入

  (*pfuncExample1)(100, 200);//関数ポインタを使った関数の利用(呼び出し)
  retrun 0;
}

上記のように、関数ポインタを利用して関数の定義、代入、呼び出しが出来ます。大事なのは戻り値の型と引数の型がすべて一致しないと代入のときに処理が失敗しますので、きちんと合わせるという事です。もうひとつ重要な事は、関数ポインタの宣言ときに変数の前に*を付けたり、利用するときにも、ポインタの中身を見るのと同様に変数の前に*をつける必要がありますが、関数ポインタ変数の場合はカッコ()を使って関数ポインタ変数全体を明示的に一つの意味をもったトークンであることがわかるように記述をしなければならないことです。


メイン関数のようなスタートアップ関数は2回呼び出してはいけないことになっているので、最初の例で_tmain関数のアドレスを取得しましたが、調子に乗って関数ポインタを使って呼び出し処理をするようなことはしてはいけません。お忘れなく。たぶん、そんなことをしようと思う人はいないと思いますが…念のため。


更に


ポインタで配列を扱うことができますので、関数ポインタも同様に配列のように扱うことができます。具体的には以下のように利用します。

#include "stdafx.h"

void example_function1(int n,int m){
    printf("example_function1 %d %d\n",n, m);  
};
void example_function2(int n,int m){
    printf("example_function2 %d %d\n",n, m);  
};
void example_function3(int n,int m){
    printf("example_function3 %d %d\n",n, m);  
};

int _tmain(int argc, _TCHAR* argv[])
{
  void (*pfuncExample[])(int,int) ={example_function1, 
                                    example_function2,
                                    example_function3};//定義と配列初期化

  (*pfuncExample[0])(100, 200);//関数ポインタを使った関数の利用(呼び出し)
  (*pfuncExample[1])(300, 400);
  (*pfuncExample[2])(500, 600);
  retrun 0;
}

という具合に使うことができます。サンプルは関数ポインタの配列利用のパターンを無理矢理に作りましたので、実用性がありませんが、配列番号だけで、動的に呼び出す関数を変えるようなプログラムにもできます。スッキリする場合もありますが、ややこしさも増幅する感じがあります。メモリの使い方が変わるので、処理速度改善が見込めるケースもあるかもしれません。本当かどうかはやったことが無いので知りません。ゴメンナサイ。


C++へ戻る