Visual Studio调试技巧
我们使用Visual Studio工具来查看C++对象的内存布局,所以需要在当前项目上右键单击选择“属性”后,打开属性页,在配置属性->C/C+±>命令行下的其它选项文本框中配置如下命令:
/d1 reportAllClassLayout
在“调试”中选“输出”,然后在下来框中选“生成”
无虚函数
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
void base1_fun1() {}
};
内存分布只有base1_1和base1_2,base1_fun1()函数的内存是在调用时动态分配
此种情况下,内存分布为
-->+------------+
| base1_1 |
+------------+
| base1_2 |
+------------+
单个类,没继承
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Base1* b1 = new Base1();
b1->base1_fun1();
return 0;
}
反汇编之后的结果
b1->base1_fun1();
007219D7 mov eax,dword ptr [b1] //把b1地址的值赋值给eax
007219DA mov edx,dword ptr [eax] //把eax对应的内存地址上的值赋值给edx,即edx内保存b1所对内存的地址
007219DC mov esi,esp
007219DE mov ecx,dword ptr [b1] //b1对应内存的地址赋值给ecx
007219E1 mov eax,dword ptr [edx] //edx对应内存的地址,即b1指针赋值给eax
007219E3 call eax //调用eax
总的来说,就是b1的首地址给eax,call eax就是调用base1_fun1()
结合vs2019上的变量监视以及类对象内存结构分析可知,b1的首地址保存的是虚函数表(__vfptr)
__vfptr的类型是void**,因此,指向的是一个数组,该数组的内部是 void * 对象,即
__vfprt-->+--------+
| void * |
+--------+
| void * |
+--------+
根据上述的代码可知
__vfptr --> +--------------+
| base1_func1()|
+--------------+
GDB调试
为了方便,将上面的堆分配改为栈分配
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Base1 b1;
b1.base1_fun1();
return 0;
}
首先用g++进行编译
g++ base.h main.cpp -g
然后用gdb进行调试
gdb ./a.out
b main
将断点停在b1.base1_func1()
然后用gdb查看内存变化
可以看一下ZTV4Base是什么
用objdump -d a.out
看一下frame_dummy
0000000000001140 <frame_dummy>:
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmpq 10c0 <register_tm_clones>
有继承,无扩展
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1
{
public:
int derive1_1;
int derive1_2;
};
int main()
{
Base1* b1 = new Base1();
b1->base1_fun1();
return 0;
}
b1->base1_fun2();
002C19D7 mov eax,dword ptr [b1]
002C19DA mov edx,dword ptr [eax]
002C19DC mov esi,esp
002C19DE mov ecx,dword ptr [b1]
002C19E1 mov eax,dword ptr [edx+4]
002C19E4 call eax
可以看到eax中放的是b1往下偏移4个字节的位置,即eax的值保存的是base2_func2()的地址
__vfprt-->+---------------+
| base1_fun1() |
+---------------+ <--eax
| base2_fun2() |
+---------------+
利用VS2019的工具,可以输出当前的类结构
class Base1 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base1_1
1> 8 | base1_2
1> +---
1>Base1::$vftable@:
1> | &Base1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1 size(20):
1> +---
1> 0 | +--- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | base1_1
1> 8 | | base1_2
1> | +---
1>12 | derive1_1
1>16 | derive1_2
1> +---
1>Derive1::$vftable@:
1> | &Derive1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
重新修改其表现形式
Derive类覆盖基类的函数
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1
{
public:
int derive1_1;
int derive1_2;
//覆盖基类函数
virtual void base1_fun1() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Derive1* d1 = new Derive1();
d1->base1_fun1();
return 0;
}
反汇编
d1->base1_fun1();
00741A5D mov eax,dword ptr [d1]
00741A60 mov edx,dword ptr [eax]
00741A62 mov esi,esp
00741A64 mov ecx,dword ptr [d1]
00741A67 mov eax,dword ptr [edx]
00741A69 call eax
可以看到call eax就是调用base1_fun1(),而该函数对应的地址是d1变量对应的首地址
通过vs2019生成内存结构如下
class Base1 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base1_1
1> 8 | base1_2
1> +---
1>Base1::$vftable@:
1> | &Base1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1 size(20):
1> +---
1> 0 | +--- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | base1_1
1> 8 | | base1_2
1> | +---
1>12 | derive1_1
1>16 | derive1_2
1> +---
1>Derive1::$vftable@:
1> | &Derive1_meta
1> | 0
1> 0 | &Derive1::base1_fun1
1> 1 | &Base1::base1_fun2
1>Derive1::base1_fun1 this adjustor: 0
重新修改其表现形式
Derive1新加了虚函数
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1
{
public:
int derive1_1;
int derive1_2;
virtual void derive1_fun1() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Derive1* d1 = new Derive1();
d1->derive1_fun1();
return 0;
}
反汇编
d1->derive1_fun1();
00F21AAD mov eax,dword ptr [d1]
00F21AB0 mov edx,dword ptr [eax]
00F21AB2 mov esi,esp
00F21AB4 mov ecx,dword ptr [d1]
00F21AB7 mov eax,dword ptr [edx+8]
00F21ABA call eax
可以看到调用的函数处于首地址+8的偏移处,查看其vs2019生成的内存结构
class Base1 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base1_1
1> 8 | base1_2
1> +---
1>Base1::$vftable@:
1> | &Base1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1 size(20):
1> +---
1> 0 | +--- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | base1_1
1> 8 | | base1_2
1> | +---
1>12 | derive1_1
1>16 | derive1_2
1> +---
1>Derive1::$vftable@:
1> | &Derive1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
1> 2 | &Derive1::derive1_fun1
重新修改其表现形式
多继承覆盖
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Base2
{
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 基类虚函数覆盖
virtual void base1_fun1() {}
virtual void base2_fun2() {}
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Derive1* d1 = new Derive1();
d1->derive1_fun1();
return 0;
}
反汇编
d1->derive1_fun1();
008E4936 mov eax,dword ptr [d1]
008E4939 mov edx,dword ptr [eax]
008E493B mov esi,esp
008E493D mov ecx,dword ptr [d1]
008E4940 mov eax,dword ptr [edx+8]
008E4943 call eax
vs2019生成的内存结构
class Base1 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base1_1
1> 8 | base1_2
1> +---
1>Base1::$vftable@:
1> | &Base1_meta
1> | 0
1> 0 | &Base1::base1_fun1
1> 1 | &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Base2 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base2_1
1> 8 | base2_2
1> +---
1>Base2::$vftable@:
1> | &Base2_meta
1> | 0
1> 0 | &Base2::base2_fun1
1> 1 | &Base2::base2_fun2
1>Base2::base2_fun1 this adjustor: 0
1>Base2::base2_fun2 this adjustor: 0
1>class Derive1 size(32):
1> +---
1> 0 | +--- (base class Base1)
1> 0 | | {vfptr}
1> 4 | | base1_1
1> 8 | | base1_2
1> | +---
1>12 | +--- (base class Base2)
1>12 | | {vfptr}
1>16 | | base2_1
1>20 | | base2_2
1> | +---
1>24 | derive1_1
1>28 | derive1_2
1> +---
1>Derive1::$vftable@Base1@:
1> | &Derive1_meta
1> | 0
1> 0 | &Derive1::base1_fun1
1> 1 | &Base1::base1_fun2
1> 2 | &Derive1::derive1_fun1
1> 3 | &Derive1::derive1_fun2
1>Derive1::$vftable@Base2@:
1> | -12
1> 0 | &Base2::base2_fun1
1> 1 | &Derive1::base2_fun2
修改其表现形式如下
如果基类没有虚函数
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
};
class Base2
{
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Derive1* d1 = new Derive1();
d1->derive1_fun1();
return 0;
}
反汇编
d1->derive1_fun1();
00561B63 mov eax,dword ptr [d1]
00561B66 mov edx,dword ptr [eax]
00561B68 mov esi,esp
00561B6A mov ecx,dword ptr [d1]
00561B6D mov eax,dword ptr [edx+8]
00561B70 call eax
vs2019生成的内存结构
class Base1 size(8):
1> +---
1> 0 | base1_1
1> 4 | base1_2
1> +---
1>class Base2 size(12):
1> +---
1> 0 | {vfptr}
1> 4 | base2_1
1> 8 | base2_2
1> +---
1>Base2::$vftable@:
1> | &Base2_meta
1> | 0
1> 0 | &Base2::base2_fun1
1> 1 | &Base2::base2_fun2
1>Base2::base2_fun1 this adjustor: 0
1>Base2::base2_fun2 this adjustor: 0
1>class Derive1 size(28):
1> +---
1> 0 | +--- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | base2_1
1> 8 | | base2_2
1> | +---
1>12 | +--- (base class Base1)
1>12 | | base1_1
1>16 | | base1_2
1> | +---
1>20 | derive1_1
1>24 | derive1_2
1> +---
1>Derive1::$vftable@:
1> | &Derive1_meta
1> | 0
1> 0 | &Base2::base2_fun1
1> 1 | &Base2::base2_fun2
1> 2 | &Derive1::derive1_fun1
1> 3 | &Derive1::derive1_fun2
修改其表达方式
这里的虚函数表指针位置是不对的,按照反汇编可知,其内存分布的图形应如下
两个基类都没有虚函数
/*
Base1.cpp
*/
class Base1
{
public:
int base1_1;
int base1_2;
};
class Base2
{
public:
int base2_1;
int base2_2;
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
Derive1* d1 = new Derive1();
d1->derive1_fun1();
return 0;
}
反汇编
d1->derive1_fun1();
00791A03 mov eax,dword ptr [d1]
00791A06 mov edx,dword ptr [eax]
00791A08 mov esi,esp
00791A0A mov ecx,dword ptr [d1]
00791A0D mov eax,dword ptr [edx]
00791A0F call eax
vs2019生成的内存结构
class Base1 size(8):
1> +---
1> 0 | base1_1
1> 4 | base1_2
1> +---
1>class Base2 size(8):
1> +---
1> 0 | base2_1
1> 4 | base2_2
1> +---
1>class Derive1 size(28):
1> +---
1> 0 | {vfptr}
1> 4 | +--- (base class Base1)
1> 4 | | base1_1
1> 8 | | base1_2
1> | +---
1>12 | +--- (base class Base2)
1>12 | | base2_1
1>16 | | base2_2
1> | +---
1>20 | derive1_1
1>24 | derive1_2
1> +---
1>Derive1::$vftable@:
1> | &Derive1_meta
1> | 0
1> 0 | &Derive1::derive1_fun1
1> 1 | &Derive1::derive1_fun2
修改表现方式如下
虚函数表指针规律
哪个类有虚函数表,哪个类就更靠前
C++利用虚函数表实现多态
根据上面的内存分析可知,任何一个类都是把成员变量按照顺序排列,但是实现的虚函数是按照虚函数表在其他的位置排列,在类的头部用虚函数表指针引导。
因此任何类的继承都可以通过这种方式实现。