4

私はC++オブジェクトモデルの内部の本を読んでいます。この本には、次のような例があります。

struct Base1
{
    int v1;
};

struct Base2
{
    int v2;
};

class Derived : public Base1, public Base2 {};

printf("&Derived::v1 = %p\n", &Derived::v1);        // Print 0 in VS2008/VS2012
printf("&Derived::v2 = %p\n", &Derived::v2);        // Print 0 in VS2008/VS2012

前のコードでは、アドレスDerived::v1とDerived::v2の出力は両方とも0になります。ただし、変数を介して同じアドレスを出力する場合:

int Derived::*p;
p = &Derived::v1;
printf("p = %p (&Derived::v1)\n", p);        // Print 0 in VS2008/VS2012 as before
p = &Derived::v2;
printf("p = %p (&Derived::v2)\n", p);        // Print 4 in VS2008/VS2012

&Derived :: v1とpのサイズを調べると、両方で4になります。

// Both are 4
printf("Size of (&Derived::v1) is %d\n", sizeof(&Derived::v1));
printf("Size of p is %d\n", sizeof(p));

Derived :: v1のアドレスは0になりますが、Derived::v2のアドレスは4になります。変数に割り当てたときに&Derived::v2が4になった理由がわかりません。

アセンブリコードを調べます。Derived::v2のアドレスを直接クエリすると、0に変換されます。ただし、変数に割り当てると、4に変換されます。

VS2008とVS2012の両方でテストしましたが、結果は同じです。ですから、マイクロソフトにそのようなデザインを選ばせるには、何らかの理由があるに違いないと思います。

そして、あなたがこれを好きなら:

d1.*(&Derived::v2) = 1;

どうやら&Derived::v20ではありません。コンパイラがこの2つのケースを区別するのはなぜですか?

誰かがその背後で起こっていることを教えてもらえますか?ありがとうございました!

- 編集 -

&Derived::v1が有効なアドレスを取得していないと思う人のために。あなたはこれをしたことがありませんか?

Derived d1, d2;
d1.*p = 1;
d2.*p = 1;
4

4 に答える 4

5

ポスターは私にこれについて尋ねました、そして最初に私は同様の間違った原因を疑った。これはVC++に固有のものではありません。

何が起こっているのかというと、のタイプはで&Derived::v2はなくint Derived::*int Base2::*Base2に対するオフセットであるため、当然、ゼロのオフセットがあります。明示的にに変換するとint Derived::*、オフセットが修正されます。

このコードをVC++、GCC、またはClangで試してください...ポスターが使用していたので、私はstdio/printfを使い続けています。

struct Base1 { int a; };
struct Base2 { int b; };
struct Derived : Base1, Base2 { };

#include <cassert>
#include <cstdio>
#include <typeinfo>
using namespace std;

int main () {

   printf( "%s\n", typeid(&Derived::a).name() );  // mentions Base1
   printf( "%s\n", typeid(&Derived::b).name() );  // mentions Base2

   int Derived::* pdi = &Derived::b;  // OK
   int Base2::*   p2i = &Derived::b;  // OK
   //int Base1::* p1i = &Derived::b;  // ERROR

   assert( sizeof(int*) == sizeof(pdi) );
   printf( "%p %p", p2i, pdi );  // prints "(nil) 0x4" using GCC 4.8 at liveworkspace.org

}
于 2013-04-01T23:38:15.640 に答える
2

あなたがしているとき、あなたは&Derived::v2有効なオブジェクトを持っていないのであなたは有効なアドレスを取得していません。ただし、2番目のケースでは、クラス内のメンバーのオフセットを取得します。つまり、タイプのオブジェクトを作成した場合、4バイト後にメモリに格納されます。Derivedv2v1Derived

于 2013-03-20T09:42:01.117 に答える
0

&Derived::v1&Derived::v2intsであるため、4バイトの長さです。これらの式の1つを割り当てるときに出力するのは、クラスpのインスタンスへのポインターからのオフセットです。Derived

于 2013-03-20T09:51:25.007 に答える
0

私が知っている情報のほとんどは、メンバー関数へのポインターについて具体的に言及していますが、メンバーデータへのポインターが別の方法で実装される理由はわかりません。

多重継承が関係するメンバー関数へのポインターは、関数ポインター(常に派生クラスの場所を指す)と、派生クラスのこのポインターがと同じでない場合を管理するためのオフセットを含む構造体として実装されることがよくあります。 thisポインタ。派生クラスを説明するために、非表示のthisパラメーターにオフセットが追加されます。あなたが見ているのは、メンバーへのポインタのタイプに応じて変化するオフセットであり、ハーブサッターの答えで雄弁に説明されています:タイプがBase2 :: *の場合のこれからのオフセットは0ですが、タイプが派生した場合のこれからのオフセット: :*は4です。

実装の詳細の詳細については、Raymond Chenのブログ投稿(2004年以降、詳細が変更されている可能性があります)を読むことをお勧めします。ここで、質問がここに提示され、ここ回答されます。これらの投稿では、sizeof()がメンバーへのポインターに対して興味深い結果を返す理由についても説明します。

于 2013-04-02T00:10:44.510 に答える