LoginSignup
19
28

More than 5 years have passed since last update.

関数ポインタのメモ

Last updated at Posted at 2016-02-10

関数ポインタのメモ

C/C++におけるポインタ/関数ポインタのメモです。
クラスのメンバ関数を関数ポインタに入れる場合などややこしいです。

いちおうGCCで動作確認しました。
C++11が混じってます。

cpp
    int testFunc(int x)
    {
        x++;
        printf("testFunc(%d)\n", x);
        return x;
    }

    class Human
    {
    public:
        static void Walk()
        {
            printf("Human walk.\n");
        };

        int age = 15;
        void Run()
        {
            printf(" run.\n");
        };
    };

    void checkUseOfPointer()
    {
        // ポインタ宣言子 *
        // 基本型に*をつけると派生型のポインタ型となる
        // int* p;とする方が文脈上わかりやすいが、ミスを減らすため慣例上int *p;とする
        // int *p;とした方が他の宣言と桁が揃うので見やすいことも理由のひとつ
        int *p = NULL;
        int n = 1;

        // 0で初期化してもNULLポインタとなる
        //int *p = 0;

        // 下記はバッドノウハウの一つなので、やってはダメ
        // pはポインタ変数だが、nはint型となる
        // 間違いの元なので、変数宣言は1行に1つが推奨される
        // 変数宣言と同時に初期化することも推奨される
        //int* p, n;

        // int型の変数のアドレスを格納するので、int *なのであって、アドレスの大きさがintということではない
        // 下記のように異なる型のアドレスにキャストすると元の情報が失われる可能性がある
        //char *c = (int *)&n;

        // void型ポインタにはどのようなポインタ型でも入れることができる
        // 呼びだすときは元の型にキャストしないと未定義の動作となる
        // void型ポインタへの代入は暗黙的変換が行われるのでキャストは省略が可能
        //void *p = (void *)&n;

        // アドレス演算子 &
        // 変数に&をつけるとアドレスを表す
        p = &n;

        // 間接演算子 *
        // ポインタ変数やアドレスに*をつけると間接参照を表す(デリファレンスされる)
        // 間接参照演算子は間接演算子(indirection)と間接参照(dereferencing)が混じっているので間違い
        *p = 5;

        // 多重間接参照(ポインタのポインタ)
        // 何らかの操作で型情報を参照できなくなるはずだが、例を見つけられず
        int **pp = &p;

        printf("n = %d (%d)\n", n, &n);
        printf("n = %d (%d)\n", *&n, &n);
        printf("*p = %d (%d)\n", *p, p);
        printf("*p = %d (%d)\n", *p, &*p);
        printf("*pp = %d (%d)\n", *pp, pp);
        printf("**pp = %d (%d)\n", **pp, *pp);

        // 数値のアドレスを直接デリファレンスすることも可能
        // 組み込み開発では、ペリフェラルのレジスタを操作するときに必須
        //printf("x = %d (%d)\n", *(0x08000010), 0x08000010);

        // void *の別名を定義
        typedef void *PVOID;

        // C/C++は関数もデータも全てアドレス空間に配置される
        // 関数もアドレスを持っているので、ポインタへ入れることができる
        // 特殊な環境だと、関数のアドレスの大きさが変わることがあるので、その場合は使えない
        void *pv = (PVOID)&testFunc;

        // PVOIDを使わないで書くと下記のようになる
        //void *pv = (void *)&testFunc;
        // 関数の場合は&をつけなくともアドレスと認識される
        //void *pv = (void *)testFunc;

        // 関数ポインタ void (*)(void)
        // pfという関数ポインタを定義する
        // int *pf(int)だと返値がポインタと解釈される
        // int (*pf)(int)と囲み、関数ポインタとする
        // 関数ポインタ型の変数はpfとなる
        // int (*pf)(int)の(int)は関数のシグネチャをあわせるために必要
        // シグネチャとは返値と引数を合わせた関数の型情報のこと
        // シグネチャだけで表現するとint (*)(int)となる
        int (*pf)(int) = &testFunc;
        //pf = &testFunc;

        // PFという別名を定義
        // 普通のtypedef a b;の使い方と違い、typedef pf;となるので注意
        typedef int (*PF)(int);
        PF pf2 = &testFunc;

        // C++11以降はusingが使える
        using usingPF = int (*)(int);
        usingPF pf3 = testFunc;

        printf("Entery point(pv) = %d\n", pv);
        printf("Entery point(pf) = %d\n", pf);
        printf("Entery point(pf2) = %d\n", pf2);
        printf("Entery point(pf3) = %d\n", pf3);

        n = 0;
        n = ((int (*)(int))ep)(n);
        n = pf(n);
        n = pf2(n);
        n = pf3(n);

        // 関数のアドレスをキャストしても動作する(未確認)
        //n = ((usingPF)(0x080023d4))(n);

        // ここからはC++でclassを使った場合
        using classPF = void (*)();
        //typedef void (*classPF)();

        // 普通の関数ポインタには、staticな静的メンバ関数しか入らない
        classPF pf4 = &Human::Walk;

        // リンカが出力するmapファイルを見ると、メンバ関数は名前空間付きで定義されている
        // つまり静的メンバ関数は、通常の関数と同じ扱いができる
        // しかしインスタンスを必要とする(非静的)メンバ関数はそのままでは使えない
        // .text._Z8testFunci
        //  0x080023d4 0x24 ./src/main.o
        //  0x080023d4 testFunc(int)
        // .text._ZN5Human4WalkEv
        //  0x080023f8 0x10 ./src/main.o
        //  0x080023f8 Human::Walk()
        // .text._ZN5Human3RunEv
        //  0x08002408 0x18 ./src/main.o
        //  0x08002408 Human::Run()

        // メンバ関数への間接演算子 ::*
        // メンバ関数を入れる関数ポインタには名前空間が必要
        using instPF = void (Human::*)();
        //typedef void (Human::*instPF)();

        instPF pf5 = &Human::Run;

        printf("Entery point(pf4) = %d\n", pf4);
        printf("Entery point(pf5) = %d\n", pf5);

        // 関数呼出演算子 ()
        // 実は括弧も演算子の一つ
        // 静的メンバ関数はそのまま呼べる
        pf4();

        // メンバへの間接演算子 .* または ->*
        // メンバ関数はインスタンスが必要
        // インスタンスはalice
        // alice.pf5()だとただのメンバ関数の呼出と解釈される
        // (alice.*pf5)()とすることで関数ポインタでの呼出となる
        Human alice;
        printf("Alice");
        (alice.*pf5)();

        // おまけ
        int Human::*x = &Human::age;
        alice.age++;
        printf("age = %d (%d)", alice.*x, &(alice.*x));

        for (;;);
    }

キーワードメモ

ポインタ宣言子 int *p
間接演算子 *p
リファレンス宣言子 int &n
アドレス演算子 &n
関数呼出演算子 ()
メンバーへの間接演算子 .* または ->*

基本型 char, short, int, long, float, double, etc...
派生型 ポインタ, リファレンス, 配列, 関数, etc...

リファレンスは派生型となるが、別名を宣言している
リファレンス型の実態としては基本型と同じとなる
int &n; // nはint型

ライブラリの実装など
標準Cライブラリ
glibc
newlib
uClibc
libstdc++
libc++

参考
JIS X3010-1993 プログラム言語C
CとC++の演算子
全てのCプログラマが未定義な振る舞いについて知っておくべきこと

19
28
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
28