虚拟表上的C ++虚拟析构函数和类的类型信息

时间:2012-08-09 07:57:57

标签: c++ oop

我总是对c ++虚拟表的内存布局感到困惑。这是一个示例代码 我用来研究它:

#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()? 0x804a248的信息是否代表类的类型信息?

其他一些信息:

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

什么是0x8048dd4用于?

2 个答案:

答案 0 :(得分:2)

首先,当然:你所做的是完全未定义的行为,并且 理论上任何事都可能发生。但我怀疑你知道这一点,而且 正在“试验”以找出编译器将要做什么。 当然,在实践中,无论你做什么,都会非常依赖 编译器 - 我猜你在英特尔上使用g ++,因为 我知道的其他编译器会有比输出更多的输出 你看到了。

至于为什么最后的输出很奇怪:还有另一个未定义的 你的行为。您使用"%p"输出,这意味着您 需要传递void*,或者您有未定义的行为。在实践中, 它也可能指向非成员函数的指针(和 Posix或多或少要求它工作),但指向成员函数 完全不同。通常,指向成员函数的指针 (如&Point::func_hs)将与某种结构相对应 一些额外的信息来表明是否有这个功能 是虚拟还是非,以及vtable中的索引(如果是函数 是虚拟的)或函数的物理地址。通常(但是 再次,这取决于实现),指针的大小 成员函数将大于指向非成员的指针的大小 (或指向静态成员的指针)。

如果您的目标是了解布局,我会将实际内存转储到 十六进制,使用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 );
}

答案 1 :(得分:1)

  

我完全不明白为什么最后一个输出是'funz_zzy:(nil)'和'funz_zzy:0x5',但上面的输出是0x5和0x11。

这是因为在一种情况下,您使用带有多个参数的printf(),而在其他情况下只使用一个参数。因为指向成员的指针似乎是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()?

看起来这与它是虚拟的有关。如果删除virtual关键字,则只生成一个析构函数。第二个与第一个相同,但它还在某些数据上调用free()函数。我没弄清楚这是为了什么。

  

0x804a248的信息是否代表类的类型信息?

这应该是typeid(Point)的结果。