1

私は常にC++仮想テーブルのメモリレイアウトについて混乱しています.Hereは私がそれを研究するために使用するコード例です:

#include <cstdio>
#include <iostream>
using namespace std;
class Point
{
public:
    Point()
    {
        cout<<"Point constructor"<<endl;
    }

    virtual void func_hs()
    {
        cout<<"Point::func_hs"<<endl;
        printf("the address of this --func_hs:%p\n",&Point::func_hs);
    }
    virtual void  func_zzy()
    {
        cout<<"Point::func_zzy"<<endl;
        printf("the address of this --func_zzy:%p\n",&Point::func_zzy);
    }


    void printVt()
    {
        printf("the address of object,this:%p\nthe address of vt:%p\n",
               this,(void*)*(int*)this);
    }
    void callVtFuncs(int num=2)
    {   
        typedef void (*Funp)(void);

        for(int i=0;i<num;i++)
        {
            Funp funp=(Funp)*((int*)*(int*)this+i);
            printf("%p\n",((int*)*(int*)this+i));
            printf("Point::callVtFuncs=>address of this fun:%p\n",funp);
            if(i==2||i==3)
            {
                continue;
            }
            funp();
        }
    }

    void printVirtualFunAddress()
    {
        cout<<endl<<endl;
        printf("func_hs:%p\nfunc_zzy:%p\n",&Point::func_hs,&Point::func_zzy);
    }
    virtual ~Point()
    {
        cout<<"Point destructor"<<endl;
    }
    virtual void  func_zzzy()
    {
        cout<<"Point::func_zzzy"<<endl;
        printf("the address of this --func_zzzy:%p\n",&Point::func_zzzy);
    }
protected:
    float x,y,z;
};


int main(int argc, char *argv[])
{
    Point point;
    point.printVt();
    point.callVtFuncs(5);
    point.printVirtualFunAddress();
    return 0;
}

クラスに 4 つの仮想関数を配置し、そこにアドレス情報を出力します。出力は次のとおりです。

Point constructor
the address of object,this:0xbffff620
the address of vt:0x8048db8


0x8048db8
Point::callVtFuncs=>address of this fun:0x8048914
Point::func_hs
the address of this --func_hs:0x1
0x8048dbc
Point::callVtFuncs=>address of this fun:0x8048966
Point::func_zzy
the address of this --func_zzy:0x5
0x8048dc0
Point::callVtFuncs=>address of this fun:0x8048b0a
0x8048dc4
Point::callVtFuncs=>address of this fun:0x8048b56
0x8048dc8
Point::callVtFuncs=>address of this fun:0x8048b74
Point::func_zzzy
the address of this --func_zzzy:0x11


func_hs:0x1
func_zzy:(nil)
func_zzzy:0x5
Point destructor

最後の出力が 'funz_zzy:(nil)' と 'funz_zzy:0x5' である理由がまったくわかりませんが、上記は 0x5 と 0x11 です。

ここにいくつかのデバッグ情報があります:(linux 32bit)

(gdb) x/16a 0x8048da8
0x8048da8:  0xa7025 0x0 0x0 0x8048dd4 <_ZTI5Point>
0x8048db8 <_ZTV5Point+8>:   0x8048914 <Point::func_hs()>    0x8048966 <Point::func_zzy()>   0x8048b0a <Point::~Point()> 0x8048b56 <Point::~Point()>
0x8048dc8 <_ZTV5Point+24>:  0x8048b74 <Point::func_zzzy()>  0x696f5035  0x746e  0x804a248 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8>
0x8048dd8 <_ZTI5Point+4>:   0x8048dcc <_ZTS5Point>  0x3b031b01  0x80    0xf

Point::~Point() が 2 つある理由がわかりません。また、0x804a248 の情報はクラスの型情報を表していますか?

その他の情報:

(gdb) x/1a 0x8048dd4
0x8048dd4 <_ZTI5Point>: 0x804a248 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8>

0x8048dd4 は何に使用されますか?

4

2 に答える 2

2

まず、もちろん、あなたがしていることは完全に定義されていない動作であり、理論上は何でも起こり得る. しかし、あなたはこれを知っていて、コンパイラが何をするかを知るために「実験」していると思います。もちろん、実際には、表示されるものはコンパイラに大きく依存します。Intel で g++ を使用していると思います。私が知っている他のコンパイラでは、表示されているものとは大幅に異なる出力が得られるからです。

最後の出力が奇妙である理由については、別の未定義の動作があります。を使用して出力しています。"%p"つまり、 を渡す必要があるvoid*か、動作が未定義です。実際には、非メンバー関数へのポインターも同様に表現する可能性があります (Posix では多かれ少なかれ動作する必要があります) が、メンバー関数へのポインターは完全に異なります。通常、メンバー関数へのポインター (&Point::func_hs) は、関数が仮想かどうかを示す何らかの追加情報と、vtable 内のインデックス (関数が仮想の場合) または関数の物理アドレスを含む、ある種の構造に対応します。通常 (ただし、これは実装によって異なります)、メンバー関数へのポインターのサイズは、非メンバーへのポインター (または静的メンバーへのポインター) のサイズよりも大きくなります。

あなたの目標がレイアウトを理解することである場合、実際のメモリを16進数でダンプし、sizeof(&Point::func_hs)などを使用します。

template <typename T>
class DumpAsUInt
{
    T const& myValue;
public:
    DumpAsUInt( T const& value ) : myValue( value ) {}
    friend std::ostream& operator<<( std::ostream& dest, DumpAsUInt const& obj )
    {
        unsigned const* p = (unsigned const*)( &obj.myValue );
        for ( int i = 0; i != sizeof(T) / sizeof(unsigned); ++ i ) {
            if ( i != 0 ) {
                dest << ' ';
            }
            dest << p[i];
        }
        return dest;
    }
};

template <typename T>
DumpAsUInt<T>
dumpAsUInt( T const& value )
{
    return DumpAsUInt<T>( value );
}
于 2012-08-09T09:00:23.023 に答える
1

最後の出力が 'funz_zzy:(nil)' と 'funz_zzy:0x5' である理由がまったくわかりませんが、上記は 0x5 と 0x11 です。

これはprintf()、複数の引数を使用する場合もあれば、1 つだけを使用する場合もあるためです。また、メンバーへのポインターは 8 バイトの長さの型のように見えるため、この余分なゼロが得られます。このコードを試してください:

printf("sizeof func:%u\n",sizeof(&main));
printf("sizeof memfunc:%u\n",sizeof(&Point::printVirtualFunAddress));
printf("sizeof virtmemfunc:%u\n",sizeof(&Point::func_zzzy));

私にとっては印刷されます

sizeof func:4
sizeof memfunc:8
sizeof virtmemfunc:8

Point::~Point() が 2 つある理由がわかりません。

これは仮想であることと関連しているようです。キーワードを削除するとvirtual、デストラクタが 1 つだけ生成されます。2 番目のものは最初のものと同じですが、追加free()で一部のデータに対して関数を呼び出します。これが何のためにあるのかわかりませんでした。

また、0x804a248 の情報はクラスの型情報を表していますか?

これは、 の結果として得られる構造である必要がありますtypeid(Point)

于 2012-08-09T08:57:28.040 に答える