C对象布局及多态实现探索之虚函数调用

C对象布局及多态实现探索之虚函数调用,第1张

C对象布局及多态实现探索之虚函数调用,第2张

我们来看虚拟成员函数的调用。C041类包含虚拟成员函数,定义如下:

struct C041
{
C041():c _(0x 01){ }
virtual void foo(){ c _ = 0x 02;}
char c _;
};
执行以下代码:

C041 obj
PRINT_DETAIL(C041,obj)
PRINT _ VTABLE _ ITEM(obj,0,0)
obj . foo();
C041 * pt = & obj;
pt-> foo();
结果如下:

C041IS14B345 0001的详细信息
obj:objadr:0012 f 824 VP ADR:0012 f 824 vt ADR:0045 b 314 vt ival(0):0041 df1 e
我们打印出了C041的对象内存布局及其虚拟表信息。

先看obj . foo();装配代码:

04230跳蚤ECX,[EBP fffff 948h]
004230 e 5 call 0041 df1 e
与上一篇文章看到的普通成员函数调用生成的汇编代码相同。这说明通过对象进行的函数调用,即使被调用的函数是虚函数,仍然是静态绑定的,即函数的地址是在编译时确定的。不会有多态行为。

让我们追踪它,看看函数的汇编代码。

01 004263F0推送ebp
02 004263F1 mov ebp,esp
03 004263F3 sub esp,0CCh
04 004263F9推送ebx
05 004263FA推送esi
06 004263FB推送edi
07 004263FC推送ecx
07 2
16 0042641 a POPEDI
17 0042641 b pope si
18 0042641 c pope bx
19 0042641d EBP MOVESP
20 0042641 f pope BP[第14行将该指针的值移动到eax寄存器,第15行将值赋给该类的第一个成员变量。 这时我们可以看到,取变量地址时使用了[eax 4],即跳过了对象布局的前4个字节的虚拟表指针。

接下来我们来看看通过指针调用的虚函数pt-> foo();,生成的汇编代码如下:

01 004230F6 mov eax,dword ptr[ebp fffff 900h]
02 004230 fc mov EDX,dword ptr[eax]
03 004230 Fe mov ESI,esp
04 00423100 mov ecx,dwptr[ebpffff 900h]
05 0042311第2行将eax中指针指向的值(注意不是eax的值)取入edx寄存器,实际上是虚拟表的地址。执行完这两条指令后,让我们看看eax和edx中的值。果然和之前打印的obj的虚拟表信息中的vpadr和vtadr的值一样,分别是0x0012F824和0x0045B314。第4行也使用ecx寄存器来存储和传递对象的地址,也就是这个指针的值。call指令的第5行,我们可以看到目的地址是一个直接的函数地址,不像通过对象调用。相反,edx中的值被用作进行间接调用的指针。我们已经知道存储在edx中的地址实际上是一个虚拟表,我们也知道虚拟表实际上是一个指针数组。这样第5行的调用实际上得到的是虚表中第一个条目的值,也就是C041::foo()函数的地址。如果被调用虚函数对应的虚表项的索引不为0,你会看到edx后的偏移值加上一个索引号乘以4。跟踪显示ptr[edx]的值为0x0041DF1E,这也与我们打印的vtival(0)的值相同。如前所述,这个地址实际上并不是一个真正的函数地址,而是一个跳转指令。如果继续执行,将会到达真正的函数代码部分(即前面列出的代码)。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » C对象布局及多态实现探索之虚函数调用

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情