我总是对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 有什么用?
最佳答案
当然,首先:您正在做的是完全未定义的行为,并且 理论上任何事情都可能发生。但我怀疑你知道这一点,并且 正在“试验”以找出编译器将做什么。 当然,在实践中,无论您看到什么,都将非常依赖于 编译器——我猜你在 Intel 上使用 g++,因为 我知道的其他编译器的输出结果会大不相同 你看到了。
至于为什么最后的输出很奇怪:还有一个undefined
对你的行为。您正在使用 "%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 );
}
关于虚拟表上的 C++ 虚拟析构函数和类的类型信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11879035/