関数ポインタのはなし

関数ポインタとは

関数をポインタ変数に入れて使えるよっていうやつです
これです

int func(void)
{
  return 0;
}

int main(int argc, char *argv[])
{
  int (*p)() = func;
  return (*p)();
}

なんでポインタに関数名を入れられるのか分かりませんでした

ですが,その理由はアセンブラにありました

00000000004004cd <func>:
  4004cd:       55                      push   %rbp
  4004ce:       48 89 e5                mov    %rsp,%rbp
  4004d1:       b8 00 00 00 00          mov    $0x0,%eax
  4004d6:       5d                      pop    %rbp
  4004d7:       c3                      retq

00000000004004d8 <main>:
  4004d8:       55                      push   %rbp
  4004d9:       48 89 e5                mov    %rsp,%rbp
  4004dc:       48 83 ec 20             sub    $0x20,%rsp
  4004e0:       89 7d ec                mov    %edi,-0x14(%rbp)
  4004e3:       48 89 75 e0             mov    %rsi,-0x20(%rbp)
  4004e7:       48 c7 45 f8 cd 04 40    movq   $0x4004cd,-0x8(%rbp)
  4004ee:       00
  4004ef:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
  4004f3:       b8 00 00 00 00          mov    $0x0,%eax
  4004f8:       ff d2                   callq  *%rdx
  4004fa:       c9                      leaveq
  4004fb:       c3                      retq
  4004fc:       0f 1f 40 00             nopl   0x0(%rax)

00000000004004cd <func>:とか00000000004004d8 <main>:とそのまんま関数名が書いてあります

これはラベルで,

任意の名前とメモリアドレスを対応付けるためのもの*1

だそうです

上記の例だと,アドレス4004cdをfuncという名前にしています
アドレス4004cdは関数の処理が始まる先頭のアドレスになっていますね

つまり,関数名はメモリアドレスということなので,ポインタに関数名を入れられるのは納得です

何が嬉しいのか

第一級関数のように扱えることです
cの関数は第一級関数ではないです
第一級関数の説明は

marycore.jp

が分かりやすかったです

関数ポインタで高階関数が作れるので,試しにmapを作ってみました

#include <stdio.h>
#include <math.h>

float *map(float *a, float (*f)(float, float))
{
  static float ret[3];
  for (int i = 0; i < 3; i++) {
    ret[i] = (*f)(a[i], 2.0);
  }
  return ret;
}

int main(int argc, char *argv[])
{
  float array[] = {1.0, 2.0, 3.0};
  float *result;


  result = map(array, powf);


  for (int i = 0; i < 3; i++) {
    printf("%f, ", array[i]);
  }
  printf("\n");
  for (int i = 0; i < 3; i++) {
    printf("%f, ", result[i]);
  }
  printf("\n");

  return 0;
}
$ gcc -std=c99 -lm mapper.c -o mapper
$ ./mapper
1.000000, 2.000000, 3.000000,
1.000000, 4.000000, 9.000000,

配列の要素数をmapが持っていなきゃいけないのが気に入らないです

まとめ

  • 関数名はアドレスなのでポインタに入れられる
  • 関数ポインタで高階関数が作れる

*1:Wikipedia アセンブリ言語#ラベル(2018/11/8 最終アクセス)