31

ポインタは単なるアドレスではありませんか?または私は何かが欠けていますか?

私はいくつかのタイプのポインターでテストしました:

  • 任意の変数へのポインターは同じです(私のプラットフォームでは8B)
  • 関数へのポインターは、変数へのポインターと同じサイズです(ここでも8B)。
  • 異なるパラメーターを持つ関数へのポインター-それでも同じ(8B)

しかし、メンバー関数へのポインターはもっと大きく、私のプラットフォームでは16Bです。

三つのこと:

  1. メンバー関数へのポインターが大きいのはなぜですか?彼らはさらにどのような情報を必要としていますか?
  2. 私の知る限り、標準ではポインタのサイズについては何も述べてませvoid*んが、ポインタの型を「含む」ことができなければなりません。言い換えれば、どのポインタもにキャストできる必要がありますvoid*よね?もしそうなら、メンバー関数へのポインタが16であるのにsizeof( void* )、なぜ8なのですか?sizeof
  3. サイズが異なるポインターの例は他にありますか(つまり、標準のプラットフォームの場合、まれで特別なプラットフォームではありません)?
4

5 に答える 5

32

最も通常の状況では、あなたはほとんど考えることができます

struct A {
    int i;
    int foo() { return i; }
};

A a;
a.foo();

なので

struct A {
    int i;
};
int A_foo( A* this ) { return this->i; };

A a;
A_foo(&a);

(のように見え始めCますよね?)したがって、ポインター&A::fooは通常の関数ポインターとまったく同じであると思います。ただし、複雑な点がいくつかあります。多重継承と仮想関数です。

だから私たちが持っていると想像してください:

struct A {int a;};
struct B {int b;};
struct C : A, B {int c;};

次のようにレイアウトされている可能性があります。

多重継承

ご覧のとおり、オブジェクトをA*またはでC*ポイントする場合は開始点をポイントしますが、オブジェクトをポイントする場合B*は中央のどこかをポイントする必要があります。

したがって、Cからいくつかのメンバー関数を継承しB、それをポイントしてから関数を呼び出す場合は、ポインターC*をシャッフルすることを知っている必要があります。thisその情報はどこかに保存する必要があります。そのため、関数ポインタにまとめられます。

これで、関数を持つすべてのクラスに対してvirtual、コンパイラは仮想テーブルと呼ばれるそれらのリストを作成します。次に、このテーブルへの追加のポインタをクラス(vptr)に追加します。したがって、このクラス構造の場合:

struct A
{
    int a;
    virtual void foo(){};
};
struct B : A
{
    int b;
    virtual void foo(){};
    virtual void bar(){};
};

コンパイラは、次のようになってしまう可能性があります。 ここに画像の説明を入力してください

したがって、仮想関数へのメンバー関数ポインターは、実際には仮想テーブルへのインデックスである必要があります。したがって、メンバー関数ポインターには、実際には1)関数ポインター、2)ポインターの調整this、および3)vtableインデックスが必要です。一貫性を保つために、すべてのメンバー関数ポインターはこれらすべてに対応できる必要があります。つまり8、ポインタ用の4バイト、調整用の4バイト、インデックス用の16バイト、合計バイト数です。

これは実際にはコンパイラ間で大きく異なるものであり、可能な最適化はたくさんあると思います。おそらく、私が説明したように実際に実装しているものはありません。

詳細については、これを参照しください(「メンバー関数ポインターの実装」までスクロールしてください)。

于 2012-08-17T13:35:53.420 に答える
7

基本的に、ポリモーフィックな動作をサポートする必要があるためです。RaymondChenによる素晴らしい記事を参照してください。

于 2012-08-17T13:37:28.927 に答える
2

いくつかの説明がここにあります: メンバー関数ポインターの基本的な表現

メンバーへのポインターは通常のポインターのように動作しますが、舞台裏ではそれらの表現はまったく異なります。実際、メンバーへのポインターは通常、特定の場合に最大4つのフィールドを含む構造体で構成されます。これは、メンバーへのポインタが、通常のメンバー関数だけでなく、仮想メンバー関数、複数の基本クラスを持つオブジェクトのメンバー関数、および仮想基本クラスのメンバー関数もサポートする必要があるためです。したがって、最も単純なメンバー関数は、2つのポインターのセットとして表すことができます。1つはメンバー関数の物理メモリアドレスを保持し、もう1つはこのポインターを保持します。ただし、仮想メンバー関数、多重継承、仮想継承などの場合、メンバーへのポインターは追加情報を格納する必要があります。したがって、あなたはすることができます」t通常のポインターへのメンバーへのポインターをキャストすることも、異なるタイプのメンバーへのポインター間で安全にキャストすることもできません。ブロッククォート

于 2012-08-17T13:55:42.000 に答える
0

ポインタと関係があると思いthisます...つまり、各メンバー関数には、それらが含まれるクラスのポインタも必要です。ポインタを使用すると、関数のサイズが少し大きくなります。

于 2012-08-17T13:38:15.260 に答える
0

メンバー関数へのポインターを次のように表す主な理由のいくつかは次の{this, T (*f)()}とおりです。

  • メンバー関数へのポインターを実装するよりも、コンパイラーでの実装が簡単です。T (*f)()

  • ランタイムコードの生成や追加の簿記は含まれません

  • それはと比較して適度によく機能しますT (*f)()

  • メンバー関数へのポインターのサイズが等しいというC++プログラマーからの十分な要求はありません。sizeof(void*)

  • 実行中のランタイムコード生成は、現在C++コードの事実上のタブーです

于 2016-08-12T10:33:46.757 に答える