C言語 | 高度な機能 | 関数ポインター

関数にもアドレスがある。関数のアドレスをポインターへ保存すると、実行時に呼び出す関数を選択できる。

関数アドレスの保存

関数ポインターは、戻り値型、ポインター名、引数型で宣言する。

戻り値型 (*識別子)(引数型リスト)

たとえば、int (*pf)(const char *) は、文字列を受け取り int を返す関数へのポインターである。括弧は重要であり、int *pf(const char *) と書くと int * を返す関数の宣言になる。

次の関数があるとする。

int Function(int , void *);

アドレスは次のように保存する。

int (*pf)(int , void *) = Function;

ポインター経由の呼び出しには、どちらの構文も使える。

i = pf(iValue , pointer);
i = (*pf)(iValue , pointer);

コード1

#include <stdio.h>

void f(void) {
 printf("Kitty on your lap\n");
}

int main() {
 void (*fp)(void) = f;
 fp();
 (*fp)();
 return 0;
}

関数ポインターはコールバックに役立つ。イベントが発生したときに呼び出す関数をシステムへ登録できる。また、複数のハンドラーを配列へ保存すると、拡張可能な設計を作れる。

コールバック関数の登録

次の例では、レジストリーにコールバックを追加、削除する。

コード2

#include <stdio.h>
#define ADD_FUNC 2
#define REMOVE_FUNC 4
#define RESULT_OK 0
#define RESULT_ERROR 1

typedef void(*REGISTERPROC)(const char * , int);

int RegisterFunc(REGISTERPROC func , int mode) {
 static int iLen = 0;
 static REGISTERPROC procs[256];
 int iCount;
 char removed;

 switch(mode) {
 case ADD_FUNC:
   if (iLen == 256) return RESULT_ERROR;
   for(iCount = 0 ; iCount < iLen ; iCount++)
     if (procs[iCount] == func) return RESULT_ERROR;
   procs[iLen++] = func;
   for(iCount = 0 ; iCount < iLen ; iCount++)
     procs[iCount]("Add" , iCount);
   break;
 case REMOVE_FUNC:
   removed = 0;
   for(iCount = 0 ; iCount < iLen ; iCount++) {
     if (procs[iCount] == func) {
       for(; iCount < iLen - 1 ; iCount++)
         procs[iCount] = procs[iCount + 1];
       iLen--;
       for(iCount = 0 ; iCount < iLen ; iCount++)
         procs[iCount]("Remove" , iCount);
       removed = 1;
       break;
     }
   }
   if (!removed) return RESULT_ERROR;
   break;
 default:
   return RESULT_ERROR;
 }
 return RESULT_OK;
}

void Function1(const char *msg , int value) { printf("Function1 : %s , %d\n" , msg , value); }
void Function2(const char *msg , int value) { printf("Function2 : %s , %d\n" , msg , value); }
void Function3(const char *msg , int value) { printf("Function3 : %s , %d\n" , msg , value); }

int main() {
 RegisterFunc(Function1 , ADD_FUNC);
 RegisterFunc(Function2 , ADD_FUNC);
 RegisterFunc(Function3 , ADD_FUNC);
 RegisterFunc(Function2 , REMOVE_FUNC);
 RegisterFunc(Function3 , REMOVE_FUNC);
 return 0;
}

REGISTERPROC はコールバックポインター型の別名である。レジストリーは最大256個のコールバックを受け取り、重複を防ぎ、項目が追加または削除されるたびに残っているコールバックへ通知する。