ポインタ虎の巻

関数ポインタの使い方

ポインタとして指すことのできるのは、通常のデータだけではない。プログラムの断片である関数でさえも、ポインタとしてポインタ変数に格納することができる。たとえば、ある条件に従って、足し算をするのか引き算をするのか変わる、というプログラムがあったとしよう。

int add( int a, int b ) {
        return a + b;
}

int sub( int a, int b ) {
        return a - b;
}

void main() {
        int cond;
        int x, y;
        .......
        if( cond == 0 ) {
                printf( "result = %d\n", add( x, y ) );
        } else if( cond == 1 ) {
                printf( "result = %d\n", sub( x, y ) );
        } else {
                printf( "I do NOT know!\n" );
        }
}

これは、関数ポインタを使うと、一つの関数呼び出しで、どちらかの適切な関数が呼び出されることを実現できる。

void main() {
        int (*func[2])(int, int);  /* 関数ポインタ配列の宣言 */
        int cond, x, y;
        func[0] = add;             /* 代入 */
        func[1] = sub;
        ............
        if( cond >= 0 && cond <= 1 ) {
                printf( "result = %d\n", (*func[cond])( x, y ) );
        } else {
                printf( "I do NOT know!\n" );
        }
}

これは、この例のような単純な例ではあまりメリットがないが、インタプリタの場合などでは、いわゆる「組み込み関数」はほとんどこの関数ポインタを使って実装される。あるいは、いわゆる「クリティカル・セクション」、少しでも動作が速いことが求められる部分で、あえてこの技が使われることもある。しかし、初心者はこの関数ポインタの理解が難しく、理解できない場合もあるので、使用には一定の節度が必要であろう。

また、ウィンドウ・システムで多用される「コールバック関数」(イベントが起きたときに実行される関数)や、シグナルハンドラ(signal を受けた時動作する関数)などの、いわゆる「イベントドリブン」な実装の場合には、実際に動作をする関数のポインタを、それを登録する関数に対する引数として渡す。たとえば、signal(2) の宣言は次のようになっている。

void (*signal(int signum, void (*handler)(int)))(int);

これは次のようにして使う。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void ctrl_C_handler( int signo ) {
        write( 2, "Ctrl+C pressed\n", 15 );
}

void main()
{
        signal( SIGINT, ctrl_C_handler );
        while( 1 );
}

これは、1度目の Ctrl+C を押した時には、メッセージが表示される。(2度目は終る。これはUNIXシグナルの仕様による。) この時の signal システムコールの第2引数はやはり関数ポインタである。

つまり、宣言は次の通りである。

関数の戻り値の型 (*ポインタ名)(引数リスト);

余分な (* ) が、これが関数ポインタであることを示し、引数リストは型を明示するだけなので、実際には型だけを明示すればよい。最初の例だと、

        int (*func[2])(int x, int y);
        int (*func[2])(int, int);

でも同じことである。しかし、これらの宣言では、戻り値型、引数の数と型は、ポインタの型チェックと同様に、厳しくチェックされる。だから、

        int (*func)(int x);
        int sub1(int);
        int sub2(int, int);
        float sub3(int);

        func = sub1;  /* 型がすべて一致しているのでOK */
        func = sub2;  /* 型が一致しないため、コンパイルエラー */
        func = sub3;  /* 型が一致しないため、コンパイルエラー */

という具合になる。これを回避するためには、当然キャストを利用できる。

        int (*func)(int x);
        float sub3(int);

        func = (int (*)(int))sub3;

かなり変則的なキャストになるが、可能ではある。(ただし、実行結果に意味があるかは保証しない。)



copyright by K.Sugiura, 1996-2006