関数へのポインタ

変数と同様に関数も実行時にはメモリ上のどこかに置かれているはずです。実は、関数の名前は、関数のアドレスを表しています。

ソースコード

源文件
  1|/* funcptr01.c */
  2|
  3|#include <stdio.h>
  4|
  5|int func(int x);
  6|
  7|int main(void)
  8|{
  9|  printf("main関数のアドレス = %p\n", main);
 10|  printf("func関数のアドレス = %p\n", func);
 11|
 12|  return 0;                                                                           
 13|}
 14|
 15|int func(int x)
 16|{
 17|  printf("x = %d\n", x);
 18|
 19|  return 0;
 20|}

実行結果

main関数のアドレス = 0x100000e70
func関数のアドレス = 0x100000e50

では、関数へのポインタはどのように宣言したらよいのでしょうか。

戻り値の型 (*ptr)();

のように宣言します。引数は関係ありません。int 型が戻り値である関数へのポインタは次のように宣言します。

int (*ptr)

ソースコード

源文件
  1|/* funcptr02.c */
  2|
  3|#include <stdio.h>
  4|
  5|int func(int);
  6|
  7|int main(void)
  8|{
  9|  int (*ptr1)(), (*ptr2)();                                                           
 10|
 11|  ptr1 = main;
 12|  ptr2 = func;
 13|
 14|  printf("main関数のアドレス = %p, ptr1 = %p\n", main, ptr1);
 15|  printf("func関数のアドレス = %p, ptr2 = %p\n", func, ptr2);
 16|
 17|  return 0;
 18|}
 19|
 20|int func(int x)
 21|{
 22|  printf("%x\n", x);
 23|  return 0;
 24|}

実行結果

main関数のアドレス = 0x100000e60, ptr1 = 0x100000e60
func関数のアドレス = 0x100000e40, ptr2 = 0x100000e40

もちろんポインタで与えられた関数を実行することもできます。

int (*ptr)();

このポインタが指している関数を実行するには、たとえば次のようにします。

(*ptr)(a,b);

実際問題、こんな変なことをする場面はあるのか?という疑問がわきませんか。実はちゃんとあるのです。DLL(Dynamic Link Library)というのをものの中には、関数が機械語に翻訳されて、すぐに実行できる形で納められています。よく使う関数などは、この DLL に納めておけばプログラムのサイズを小さくすることができます。

DLL 中の関数をプログラム中で呼ぶ出すには、「暗黙の呼ぶ出し」と「明示的呼び出し」というのがあります。明示的呼ぶ出しでは、プログラム側でその関数が必要になったとき初めて呼ぶ出します。このとき、その関数のアドレスを取得するよいうわけです。

今までは、関数のプロトタイプ宣言をプログラムの最初のほうの、関数外に書きました。こうすることによって、すべての関数からその関数が見えるからです。

これを、関数の中で宣言することも可能です。ポインタで与えられた関数の実行を確認するのと同時に、関数内でのプロトタイプ宣言の例を見てみましょう。

ソースコード

源文件
  1|/* funcptr03.c */                                                                     
  2|
  3|#include <stdio.h>
  4|
  5|int main(void)
  6|{
  7|  int myfunc(char *str1, char *str2);
  8|  int (*ptr)();
  9|
 10|  ptr = myfunc;
 11|
 12|  (*myfunc)("田中", "学生");
 13|  (*ptr)("山田", "会社員");
 14|  myfunc("佐藤", "公務員");
 15|
 16|  return 0;
 17|}
 18|
 19|int myfunc(char *str1, char *str2)
 20|{
 21|  printf("%sさんは、%sです\n", str1, str2);
 22|  return 0;
 23|}

実行結果

田中さんは、学生です
山田さんは、会社員です
佐藤さんは、公務員です

さて、このプログラムでは、main 関数より手前にある関数からは myfunc 関数が見えません。したがって次のプログラムはエラーとなります。

  1|#include <stdio.h>
  2|
  3|int yourfunc(){
  4|  myfunc("後藤", "浪人");
  5|  return 0;
  6|}
  7|
  8|int main(void)
  9|{
 10|  int myfunc(char *, char *);
 11|  ...
 12|    return 0;
 13|}
 14|
 15|int myfunc(char *str1, char *str2)
 16|{
 17|  ...
 18|  return 0;                                                                           
 19|}

さて、当然のことながら関数へのポインタを配列にすることも可能です。

関数の戻り値の型(*ptr[要素の個数])();

ソースコード

源文件
  1|/* funcptr04.c */                                                                     
  2|
  3|#include <stdio.h>
  4|#include <stdlib.h>
  5|
  6|#define PAI 3.141592653589793
  7|
  8|double sankaku();
  9|double daikei();
 10|double en();
 11|
 12|int main(void)
 13|{
 14|  double (*fn[3])() = {sankaku, daikei, en};
 15|  char ret[8];
 16|  int fnno;
 17|
 18|  while(1){
 19|    printf("どの面積を求めますか\n"
 20|           "(1:三角形 2:台形 3:円 0:終了)--");
 21|    scanf("%s", ret);
 22|    if(ret[0] < '0' || ret[0] > '3'){
 23|      printf("番号が不正です\n");
 24|      continue;
 25|    }                                                                                 
 26|    ret[1] = '\0';
 27|    fnno = atoi(ret);
 28|    if(fnno == 0){
 29|      break;
 30|    }
 31|    printf("面積は%fです\n", (*fn[fnno - 1]) () );
 32|  }
 33|  return 0;
 34|}
 35|
 36|double sankaku()
 37|{
 38|  char str[32];
 39|  double teihen, takasa;
 40|
 41|  printf("三角形の面積を求めます\n");
 42|  printf("底辺 = ");
 43|  scanf("%s", str);
 44|  teihen = atof(str);
 45|  printf("高さ = ");                                                                  
 46|  scanf("%s", str);
 47|  takasa = atof(str);
 48|
 49|  return teihen * takasa / 2.0;
 50|}
 51|
 52|double daikei()
 53|{
 54|  char str[32];
 55|  double jotei, katei, takasa;
 56|
 57|  printf("台形の面積を求めます\n");
 58|  printf("上底 = ");
 59|  scanf("%s", str);
 60|  jotei = atof(str);
 61|  printf("下底 = ");
 62|  scanf("%s", str);
 63|  katei = atof(str);
 64|  printf("高さ = ");
 65|  scanf("%s", str);                                                                   
 66|  takasa = atof(str);
 67|
 68|  return (jotei + katei) * takasa / 2.0;
 69|}
 70|
 71|double en()
 72|{
 73|  char str[32];
 74|  double hankei;
 75|
 76|  printf("円の面積を求めます\n");
 77|  printf("半径 = ");
 78|  scanf("%s", str);
 79|  hankei = atof(str);
 80|
 81|  return (hankei * hankei) * PAI;
 82|}

実行結果

どの面積を求めますか
(1:三角形 2:台形 3:円 0:終了)--1
三角形の面積を求めます
底辺 = 2
高さ = 2
面積は2.000000です
どの面積を求めますか
(1:三角形 2:台形 3:円 0:終了)--2
台形の面積を求めます
上底 = 6
下底 = 7
高さ = 2
面積は13.000000です
どの面積を求めますか
(1:三角形 2:台形 3:円 0:終了)--3
円の面積を求めます
半径 = 2
面積は12.566371です
どの面積を求めますか
(1:三角形 2:台形 3:円 0:終了)--0

(*fn [fnno - 1])()を実行して、その戻り値を表示します。ユーザーの選んだ番号により、実行される関数が異なるというわけです。


Chapter10 @ C言語目録 @ HomeWork List @ 昭亮's Homepage