関数ポインタのはなし
関数ポインタとは
関数をポインタ変数に入れて使えるよっていうやつです
これです
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の関数は第一級関数ではないです
第一級関数の説明は
が分かりやすかったです
関数ポインタで高階関数が作れるので,試しに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が持っていなきゃいけないのが気に入らないです
まとめ
- 関数名はアドレスなのでポインタに入れられる
- 関数ポインタで高階関数が作れる