- 関数ポインタの書き方と読み方
- 関数テーブルの書き方と読み方
- 現場ではどこで使うのか?
- 現場で使うときの注意点
関数ポインタと関数テーブルは、現場でのコードにはよく出てきますが、C言語の入門書には載っていないことが多いです。
そのため、この記事では関数ポインタと関数テーブルについて詳しく説明していきます!
また、関数ポインタを現場で使う際の注意点も説明しますので、ぜひ最後まで見てください!
【前提知識】関数にもアドレスが存在する
※タイトルを見て意味が分かる人は飛ばしてください
変数はアドレスが存在しており、ポインタでよくアドレスを指定しますよね。
実は変数にアドレスが存在するように、関数自体にもアドレスが存在しています。
試しに以下のソースコードを書いて、アドレスを表示させてみました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); int main() { printf("SumFuncのアドレス...%p\n",SumFunc); printf("SubtractionFuncのアドレス...%p\n",SubtractionFunc); printf("MultiplicationFuncのアドレス...%p\n",MultiplicationFunc); printf("DivisionFuncのアドレス...%p\n",DivisionFunc); return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
ぼくの環境だと、以下のようなアドレスが表示されました。
|
1 2 3 4 |
SumFuncのアドレス...0x7f282b0006b5 SubtractionFuncのアドレス...0x7f282b0006c9 MultiplicationFuncのアドレス...0x7f282b0006db DivisionFuncのアドレス...0x7f282b0006ee |
ところで、ポインタの変数にアドレスを指定すれば、ポインタの変数経由で値を変えたり参照することができましたよね。
以下のサンプルコードだと、ポインタの変数ptrが変数aaaのアドレスを持っているため、ptrの値を変えることでaaaの値を変えることができました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include<stdio.h> int main() { int aaa; int* ptr; /* 変数aaaに10を代入 */ aaa = 10; /* 変数aaaのアドレスをptrに代入 */ ptr = &aaa; /* ptrの参照先を77を代入 */ *ptr = 77; printf("aaa...%d\n",aaa); printf("ptr...%d\n",*ptr); return 0; } |
|
1 2 |
aaa...77 ptr...77 |
関数ポインタも、同じような考え方です。
関数のアドレスを格納する変数(ポインタ)を作って関数のアドレスを格納すれば、変数(ポインタ)を呼ぶことで関数が実施されそうですよね。
では実際に関数のアドレスを入れる変数を作成して、実行してみましょう!
関数ポインタとは?
関数ポインタとは、関数のアドレスを保持しておくポインタのことです。
関数のアドレスを変数に代入することによって、変数のように関数を使用することができます。
|
1 2 3 4 5 6 7 |
void funcA(void) { printf("A\n"); } void (*fp)(void); //関数ポインタの代入するための変数を宣言 fp = funcA; //関数ポインタの代入 fp(); //関数ポインタの実行 |
ポイントは、関数の実行タイミングを後から選んで実行できるという点です。
ここを押さえた上で、関数ポインタの使い方の説明を見ていきましょう。
関数ポインタの書き方について
関数ポインタ(関数のアドレスを入れる変数)の作成方法ですが、ちょっとややこしくて、以下のように書きます。
戻り値の型 (*ポインタの変数名)(引数)
これだけ見ても、パッと分かりにくいと思うので、まずはサンプルコードを見てください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); int main() { int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ int answer; /* 演算結果 */ /* 足し算の関数のアドレスを代入 */ ptr_func = SumFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 引き算の関数のアドレスを代入 */ ptr_func = SubtractionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* かけ算の関数のアドレスを代入 */ ptr_func = MultiplicationFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 割り算の関数のアドレスを代入 */ ptr_func = DivisionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
|
1 2 3 4 |
answer...12 answer...8 answer...20 answer...5 |
このように関数のポインタにアドレスを入れることで、直接関数を呼ばなくても関数を実行することができます。
では、少しずつ解説していきますね!
手順1. 関数ポインタ(関数のアドレスを代入する変数)を作る
まずは、関数ポインタ(関数のアドレスを代入する変数)を定義する必要があります。
定義の方法は、先ほど説明した通り、以下のような形で作ります。
戻り値の型 (*ポインタの変数名)(引数)
先ほどのサンプルコードだと、以下の部分ですね。
|
1 |
int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ |
int型の戻り値を返す関数のアドレスを代入する予定なので、まずはint型を指定します。
その後のptr_funcというのが変数名で、(int, int)の部分が引数の部分です。引数は、(int a, int b)のように変数名の記載は必要なく、省略可能です。
これで変数名ptrには「戻り値はint型で、int型の変数2つを引数とする関数のアドレスを格納する変数」という意味を持たせることができました。
手順2. 変数に関数のアドレスを代入
関数ポインタを作成したら、続いては関数のアドレスを実際に代入しましょう。
先ほどのサンプルコードだと、以下のような箇所になります。
|
1 |
ptr_func = SumFunc; |
関数のアドレスを指定する場合は、関数の前に”&”付けても付けなくてもどちらでも大丈夫です。
これで関数ポインタptr_funcには、SumFunc関数のアドレスを代入することができました。
これで後は、関数ポインタptr_funcを使って関数を呼び出すだけですね。
手順3. 関数ポインタで関数を呼び出す
関数ポインタで関数を呼び出すときは、関数ポインタに引数を指定するだけです。
先ほどのサンプルコードだと、以下のような箇所になります。
|
1 |
answer = ptr_func(10,2); |
今までの関数の呼び出し方と同じで、関数名が関数ポインタ名に変わっただけですね。
このように、関数ポインタにアドレスを代入して、関数を呼び出すことが出来ます。
関数ポインタのメリット
以下、先ほどのサンプルコードを抜粋したものです。黄色に塗られているところに注目してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
int main() { int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ int answer; /* 演算結果 */ /* 足し算の関数のアドレスを代入 */ ptr_func = SumFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 引き算の関数のアドレスを代入 */ ptr_func = SubtractionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* かけ算の関数のアドレスを代入 */ ptr_func = MultiplicationFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 割り算の関数のアドレスを代入 */ ptr_func = DivisionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); return 0; } |
実際に関数を呼び出す箇所は全て「answer = ptr_func(10,2);」というように、同じ記載になっていますね。
関数ポインタptr_funcのアドレスが、どの関数を指定しているかによって呼び出す関数が変わってきますが、関数の呼び出し方法は全く同じになります。
ここが、関数ポインタのメリットです。
そして、この関数ポインタのメリットを応用したのが、「関数テーブル」になります。
今のサンプルコードだと、足し算・引き算・かけ算・割り算の4つの関数を呼んでいますが、処理がすごく似ていますよね。
それぞれ違っているところは、関数ポインタptr_funcに各関数のアドレスを指定する処理ぐらいです。
「じゃあ、それぞれの関数のアドレスを配列にして、ループ処理で回せばコードが短くなるんじゃないか?」
こう考えたくなりますよね。なので、各関数のテーブル(配列)を作って、コードを短くしてみましょう。
関数テーブルの書き方について
関数テーブルとは、関数のアドレスが配列で定義されたものになります。
先ほどのサンプルコードだと、足し算・引き算・かけ算・割り算の4つの関数がありますので、これを配列で定義するとこうなります。
|
1 2 |
/* 関数テーブルの作成 */ int (*ptr_funcArray[4])(int,int) = {SumFunc, SubtractionFunc, MultiplicationFunc, DivisionFunc}; |
変数名の部分がすごくややこしいですが、先ほどの関数ポインタの書き方に配列の”[]”が付いただけです。
この変数ptr_funcArrayが、各関数のアドレスを配列で持ったもの、つまり関数テーブルになります。
ptr_FuncArray[0]を指定すればSumFunc関数のアドレスが、ptr_FuncArray[1]を指定すればSubtractionFunc関数のアドレスが指定されます。
なので、これをループ処理で全て呼び出そうとすると、以下のようなサンプルコードになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); /* 関数テーブルの作成 */ int (*ptr_funcArray[4])(int,int) = {SumFunc, SubtractionFunc, MultiplicationFunc, DivisionFunc}; int main() { int answer; /* 演算結果 */ int i; /* ループ処理用変数 */ for(i = 0; i < 4; i++) { answer = ptr_funcArray[i](10,2); printf("answer...%d\n",answer); } return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
実行結果は同じですが、かなり短くなりましたね。
黄色になっているところが、関数テーブルからアドレスを引っ張ってきた後、引数の(10,2)を指定して関数を呼び出している箇所になります。
現場で関数ポインタを使う場面はあるのか?
ここからは、現場目線で関数ポインタについて話していこうと思います。
まず、「関数ポインタを使う場面が実際にあるのかどうか?」なのですが、正直関数ポインタを使ってコードを書く機会は少ないと思います。
しかし、関数ポインタを使っているコードを見る機会は多くあります。
Grepで変数や関数のコール元を追っていくと、関数ポインタが使われていたということはよくありました。
関数ポインタの知識がないと、コードを追うときに訳が分からなくなると思うので、使う場面は無くとも知っておいて欲しい知識です。
関数ポインタを現場で使うときの注意点
現場でコードを書くとき、関数ポインタを使わずに書けることが大半なので、基本使いません。
なので、もし現場で関数ポインタを使ってコードを書くときは、ちゃんとリーダーに納得がいくような説明を用意しておくことが必要かなと思います。「何となく使ってみました」という理由だと、多分指摘されます。
「ここは関数ポインタを使った方が良い」という明確な理由があれば、レビューでもちゃんと議論してもらえますので、しっかり検討した上で関数ポインタを使うようにしましょう。
また、現場ごとにコーディング規約があると思いますが、規約の中に関数ポインタを使うのは禁止ということが書かれているかもしれません。
ですので、コーディング規約をまずさっと読んでみて、関数ポインタを使ってもいいのか、使う際の注意点が書かれていないかを見てから、検討した方が良いのかなと思います。
最後に
関数ポインタと関数テーブルについて解説しました。
関数ポインタのコードをいざ書こうとすると、書き方を忘れてしまうと思いますが、あんまり自分で書くことはないと思います。
どちらかと言うと、「ソースコードを追っていたら、関数ポインタになっていた」みたいな場面が多いはずです。
なので、まずは関数ポインタについて理解できれば十分かなと思います。

