滴水-day32-动态绑定
struct Sub
{
int x;
Sub()
{
this->x = 100;
}
void Fun1()
{
cout << "Sub_1_Fun1" << endl;
}
virtual void Fun2()
{
cout << "Sub_2_Fun2" << endl;
}
};
struct Base1:Sub
{
int x;
Base1()
{
this->x = 200;
}
void Fun1()
{
cout << "Base1_1_Fun1" << endl;
}
virtual void Fun2()
{
cout << "Base2_2_Fun2" << endl;
}
};
void Test(Sub* sub)
{
int n = sub->x;
cout << n << endl;
sub->Fun1();
sub->Fun2();
}
分析代码
在这个代码中,Base1继承了Sub结构体
在main函数下断点找到Test函数地址查看反汇编
0041EB85 8B 45 08 mov eax,dword ptr [sub]
0041EB88 8B 48 04 mov ecx,dword ptr [eax+4]
0041EB8B 89 4D F8 mov dword ptr [n],ecx
给n局部变量赋值,这里传入了sub的this指针赋值给了eax,接着eax+4取值赋值给了ecx,这里加4是为了跳过结构体首地址的虚函数地址,eax+4就取到了第一个成员变量的值。这里是取sub的成员变量,也就是父类。所以n的值就是父类的成员变量的值。
<aside> 💡
这里的n不管传入的对象是谁,始终会调用父类的n不会调用子类,因为Test函数的参数类型是Sub
</aside>
sub->Fun1();
0041EBBD 8B 4D 08 mov ecx,dword ptr [sub]
0041EBC0 E8 AC 26 FF FF call Sub::Fun1 (0411271h)
传入sub的this指针,调用了sub的fun1函数,这个是普通函数直接调用,这里的地址已经写死了。
父类的fun1方法而不是子类的
sub->Fun2();
0041EBC5 8B 45 08 mov eax,dword ptr [sub]
0041EBC8 8B 10 mov edx,dword ptr [eax]
0041EBCA 8B F4 mov esi,esp
0041EBCC 8B 4D 08 mov ecx,dword ptr [sub]
0041EBCF 8B 02 mov eax,dword ptr [edx]
0041EBD1 FF D0 call eax
还是一样的,sub结构体的this指针取首地址元素,也就是虚函数地址,取出函数地址赋值给了eax,但是这里的eax的值不是固定的,如果传入了父类的指针那么这里调用的就是父类的虚函数,如果传入子类对象那么这里就调用子类的虚函数。
执行结果
当传入Sub对象时输出结果:
100 Sub_1_Fun1 Sub_2_Fun2
当传入Base1对象时输出结果:
n的赋值:
004133A5 8B 45 08 mov eax,dword ptr [sub]
004133A8 8B 48 04 mov ecx,dword ptr [eax+4]
004133AB 89 4D F8 mov dword ptr [n],ecx
还是父类的成员变量
004133DD 8B 4D 08 mov ecx,dword ptr [sub]
004133E0 E8 A5 DE FF FF call Sub::Fun1 (041128Ah)
父类的Fun1
0041EE35 8B 45 08 mov eax,dword ptr [sub]
0041EE38 8B 10 mov edx,dword ptr [eax]
0041EE3A 8B F4 mov esi,esp
0041EE3C 8B 4D 08 mov ecx,dword ptr [sub]
0041EE3F 8B 02 mov eax,dword ptr [edx]
0041EE41 FF D0 call eax
经过调试发现,这里的sub传递不一定是父类的this指针,传递的参数是谁就是谁的this指针,这里可能是vs没有识别好,说白了就是参数是谁的就传谁的虚函数地址表。
这也充分说明了虚函数在调用的时候具体调用谁的虚函数是不确定的,需要动态获取,就是调用函数地址和实际调用函数地址不固定。
CALL 0x12345678
在参数类型传递不一样的时候0x12345678保存的数据就不一样,调用的函数也就不一样。
这个过程就称为动态绑定,动态绑定还有一个名字就是:多态。