cpp 类的内存结构
说明:
-
虚表指针总是存在在类的头部,并按类的继承顺序排放。一个子类可以有多个虚表指针,且虚指针个数和具有虚函数的基类个数相同。
-
虚成员函数总是按照声明顺序存在于虚表中。
-
如果存在同名函数,子类虚函数会覆盖每一个父类的每一个同名虚函数。
-
子类独有的虚函数填入第一个虚函数表中,且用父类指针是不能调用。
-
父类独有的虚函数不会被覆盖覆盖。仅子类和该父类指针能调用。
如下图类的内存结构图
无虚函数
class Drive
{
public:
// virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
cout << sizeof(d) << endl;
return 0;
}
如下,类中没有虚函数,只有一个成员函数,以及其他默认构造析构函数。类似于空类,类的大小为1(注意空类大小不为0,因为为0的话,实例化后没法区分)。因此可以的出结论类的非虚成员函数信息不存在于对象实例中!
Reading symbols from ./a.out...
(gdb) b 17
Breakpoint 1 at 0x1197: file demo.cpp, line 17.
(gdb) r
Starting program: /home/lester/Wdir/test/a.out
1
Breakpoint 1, main () at demo.cpp:17
17 return 0;
(gdb) p d
$1 = {<No data fields>}
(gdb) $1 = {<No data fields>}
(gdb) q
A debugging session is active.
Inferior 1 [process 54798] will be killed.
Quit anyway? (y or n) y
~/Wdir/test >>> ./a.out
1
~/Wdir/test >>>
无继承
代码:
class Drive
{
public:
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
如下,子类经过强制类型转换,得到虚表指针,并提取虚表指针的内容,经过转换可以得到第一个虚函数。虚表中只有一个虚函数。
(gdb) b 69
Breakpoint 1 at 0x13c1: file xx.cpp, line 69.
(gdb) r
Starting program: /home/lester/tools/xxx/a.out
Breakpoint 1, main () at xx.cpp:69
69 return 0;
(gdb) p d
$1 = {_vptr.Drive = 0x555555557da0 <vtable for Drive+16>}
(gdb) p (int64_t*)**(int64_t**)&d
$2 = (int64_t *) 0x55555555543a <Drive::vf()>
(gdb)
单继承
class Base1
{
public:
virtual void vb1f() {}
virtual void vf() {}
};
class Drive : public Base1
{
public:
virtual void vdf() {}
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
虚表中只有多个虚函数。顺序是父类,子类的顺序。其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。
(gdb) b 70
Breakpoint 1 at 0x13c1: file xx.cpp, line 70.
(gdb) r
Starting program: /home/lester/tools/xxx/a.out
Breakpoint 1, main () at xx.cpp:70
70 return 0;
(gdb) p (int64_t*)*(*((int64_t**)&d)+0)
$1 = (int64_t *) 0x55555555543a <Base1::vb1f()>
(gdb) p (int64_t*)*(*((int64_t**)&d)+1)
$2 = (int64_t *) 0x555555555452 <Drive::vf()>
(gdb) p (int64_t*)*(*((int64_t**)&d)+2)
$3 = (int64_t *) 0x555555555446 <Drive::vdf()>
(gdb)
多继承
class Base1
{
public:
virtual void vb1f() {}
virtual void vf() {}
};
class Base2
{
public:
virtual void vb2f() {}
virtual void vf() {}
};
class Drive : public Base1, Base2
{
public:
virtual void vdf() {}
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
虚表中只有多个虚函数。顺序是父类Base1, 父类Base2,子类。
查看第一个虚表:其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。另外子类的虚函数 ”vdf“ 被放在了第一个虚表的后面。
查看第二个虚表:第二个虚表指针在地一个虚表指针后面。同样方式可以看到第二个虚表只有父类 Base2 的虚成员函数,而且共有的虚函数被子类的虚函数 vf 覆盖。
(gdb) b 71
Breakpoint 1 at 0x13cc: file xx.cpp, line 71.
(gdb) r
Starting program: /home/lester/tools/xxx/a.out
Breakpoint 1, main () at xx.cpp:71
71 return 0;
(gdb)
(gdb) p d
$10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
(gdb) p (int64_t*)*(*((int64_t**)&d)+0)
$1 = (int64_t *) 0x555555555446 <Base1::vb1f()>
(gdb) p (int64_t*)*(*((int64_t**)&d)+1)
$2 = (int64_t *) 0x55555555546a <Drive::vf()>
(gdb) p (int64_t*)*(*((int64_t**)&d)+2)
$3 = (int64_t *) 0x55555555545e <Drive::vdf()>
(gdb) p d
$10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
(gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+0)
$12 = (int64_t *) 0x555555555452 <Base2::vb2f()>
(gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+1)
$13 = (int64_t *) 0x555555555475 <non-virtual thunk to Drive::vf()>
(gdb)
虚继承(菱形继承)
单虚继承情况和单继承完全一样,这里忽略,直接描述虚继承的菱形继承情况
class Base
{
public:
virtual void vbbf() {}
virtual void vbf() {}
};
class Base1 : virtual public Base
{
public:
virtual void vb1f() {}
virtual void vf() {}
};
class Base2 : virtual public Base
{
public:
virtual void vb2f() {}
virtual void vf() {}
};
class Drive : virtual public Base1, virtual public Base2
{
public:
virtual void vdf() {}
virtual void vf() {}
void f() {}
};
int main()
{
Base1 b1;
Base2 b2;
Drive d;
return 0;
}
虚多继承
Breakpoint 1 at 0x1194: file xx.cpp, line 41.
(gdb) r
Starting program: /home/lester/Wdir/src/myself/test/a.out
Breakpoint 1, main () at xx.cpp:41
41 return 0;
(gdb) p (int64_t*)*((int64_t*)d+0)
$1 = (int64_t *) 0x55555555520e <Base::vbbf()>
(gdb) p (int64_t*)*((int64_t*)d+1)
$2 = (int64_t *) 0x55555555521a <Base::vbf()>
(gdb) p (int64_t*)*((int64_t*)d+2)
$3 = (int64_t *) 0x555555555226 <Base1::vb1f()>
(gdb) p (int64_t*)*((int64_t*)d+3)
$4 = (int64_t *) 0x555555555262 <Drive::vf()>
(gdb) p (int64_t*)*((int64_t*)d+4)
$5 = (int64_t *) 0x555555555256 <Drive::vdf()>
(gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+0)
$7 = (int64_t *) 0x0
(gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+1)
$11 = (int64_t *) 0x0
(gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+2)
$8 = (int64_t *) 0x55555555523e <Base2::vb2f()>
(gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+3)
$9 = (int64_t *) 0x55555555526d <virtual thunk to Drive::vf()>
(gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+4)
$10 = (int64_t *) 0x555555557ad8 <vtable for Drive+72>
(gdb)
如上可以看到地一个虚表和预期完全一样,按照继承的顺序,虚函数的顺序存在虚表中。
但是第二个虚表就不一样了,第二个虚表的前两个成员都是空的,并不是指向 Base 的虚函数,所以可以知道编译器在这里做了处理,避免了菱形继承中尴尬的情况。