首页
社区
课程
招聘
[原创]底层分析C++虚函数、this、多态
2023-9-3 22:16 9048

[原创]底层分析C++虚函数、this、多态

2023-9-3 22:16
9048

1.引言

本文介绍以下4个部分:
(1)虚函数
(2)虚函数表
(3)直接调用private修饰的虚函数、传递任意this指针给调用的函
(4)多态、this、虚函数表

2.虚函数

定义:简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。

3.虚函数表

虚函数表首地址存在于class对象的起始位置。虚函数可以被继承,子类重写的虚函数会覆盖虚函数表中父类的虚函数的地址。
通过this来查对应的虚函数表来实现多态。

4.接调用private修饰的虚函数

4.1无this指针

通过函数指针指向虚函数表中需要调用的虚函数地址,作为普通函数进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stdio.h"
 
class A
{
private:
    virtual void AF() {
        printf("AF run...\n");
    }
};
 
int main()
{
    A a;
    typedef void(*Myfunction)();
    Myfunction f = (Myfunction)(*(int*)(*(int*)(&a)));
    f();
}
运行结果:AF run...

4.2修复this指针

(1)在调用成员函数的时候,编译器隐式地传递了this指针这个参数,this不是通过栈进行的传递,而是通过ecx寄存器传值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stdio.h"
class A
{
public:
    int a = 1;
private:
    virtual void AF() {
        printf("AF run...\n");
        printf("a=%d\n", this->a);
    }
public:
    virtual void test(int a) {
        printf("test run...%x\n", a);
    }
};
 
int main()
{
    A a;
    a.test(0x12345678);
}
运行结果:test run...12345678
 
——————————————————————————————汇编代码————————————————————————————————
    A a;
00B51B4F 8D 4D F0             lea         ecx,[a]           // 将a的地址(this指针)赋值给ecx
00B51B52 E8 AA F8 FF FF       call        A::A (0B51401h)    // 调用A的构成函数A
    a.test(0x12345678);
00B51B57 68 78 56 34 12       push        12345678h         // 将参数a=0x12345678压入栈
00B51B5C 8D 4D F0             lea         ecx,[a]           // 将this复制给ecx
00B51B5F E8 01 F9 FF FF       call        A::test (0B51465h) // 调用函数test

(2)修复this指针,在函数中读取class A的成员变量a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "stdio.h"
class A
{
public:
    int a = 1;
private:
    virtual void AF() {
        printf("AF run...\n");
        printf("a=%d\n", this->a);
    }
};
 
int main()
{
    A a;
    A* pA = &a;
    typedef void(*Myfunction)();
    Myfunction f = (Myfunction)(*(int*)(*(int*)(&a)));
    __asm
    {
        mov ecx, pA;    // a的地址(this指针)赋值给eax
    }
    f();
}
运行结果:
AF run...
a=1

(3)传递任意class的this给调用的虚函数

调用A的AF函数,传递B的this,实现AF函数中读取出B的成员变量。

需要注意的是,在传递B的this指针时,要算好this+offset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "stdio.h"
class A
{
public:
    int a = 1;
private:
    virtual void AF() {
        printf("AF run...\n");
        printf("a=%d\n", this->a);
    }
};
 
class B: public A
{
public:
    int a = 2;
private:
    virtual void AF() {
        printf("BF run...\n");
        printf("a=%d\n", this->a);
    }
};
 
int main()
{
    A a;
    B b;
    typedef void(*Myfunction)();
    Myfunction f = (Myfunction)(*(int*)(*(int*)(&a)));
    B* pA = &b;
    __asm
    {
        mov ecx, pA;    // 调用A的AF函数,传递B的this
        add ecx, 4;     // 这里需要this+4,如果不+4,则a=1
    }
    f();
}
运行结果:
AF run...
a=2

5.多态

静态多态:子类重写父类的相同函数

动态多态:父类指针指向子类对象,调用子类虚函数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "stdio.h"
class A
{
public:
    int a = 1;
public:
    virtual void AF() {
        printf("AF run...\n");
        printf("a=%d\n", this->a);
    }
};
 
class B: public A
{
public:
    int a = 2;
public:
    virtual void AF() {
        printf("BF run...\n");
        printf("a=%d\n", this->a);
    }
};
 
int main()
{
    A a;
    B b;
    A* ptr;
 
    ptr = &a;
    ptr->AF();
 
    ptr = &b;
    ptr->AF();
     
    a.AF();
}
运行结果:
AF run...
a=1
BF run...
a=2
AF run...
a=1  
——————————————————————————————汇编代码————————————————————————————————
  A a;
00351D1F 8D 4D F0             lea         ecx,[a] 
00351D22 E8 DA F6 FF FF       call        A::A (0351401h) 
    B b;
00351D27 8D 4D DC             lea         ecx,[b] 
00351D2A E8 10 F5 FF FF       call        B::B (035123Fh) 
    A* ptr;
 
    ptr = &a;
00351D2F 8D 45 F0             lea         eax,[a]                  // 把a的地址(this指针)赋值给eax
00351D32 89 45 D0             mov         dword ptr [ptr],eax       // 把eax的值赋值给ptr指向的地址
    ptr->AF();
00351D35 8B 45 D0             mov         eax,dword ptr [ptr]       // 把ptr指向地址的值赋值给eax -> eax == this
00351D38 8B 10                mov         edx,dword ptr [eax]       // 取a的前4个字节 -> 虚函数表首地址
00351D3A 8B F4                mov         esi,esp                  // 无关的调试代码
00351D3C 8B 4D D0             mov         ecx,dword ptr [ptr]       // a的this指针赋值给ecx
00351D3F 8B 02                mov         eax,dword ptr [edx]       // 取a的第一个虚函数AF的地址
00351D41 FF D0                call        eax                      // 间接调用虚函数AF
00351D43 3B F4                cmp         esi,esp                  // 无关的调试代码
00351D45 E8 63 F5 FF FF       call        __RTC_CheckEsp (03512ADh) // 无关的调试代码
 
    ptr = &b;
00351D4A 8D 45 DC             lea         eax,[b] 
00351D4D 89 45 D0             mov         dword ptr [ptr],eax 
    ptr->AF();
00351D50 8B 45 D0             mov         eax,dword ptr [ptr] 
00351D53 8B 10                mov         edx,dword ptr [eax] 
00351D55 8B F4                mov         esi,esp 
00351D57 8B 4D D0             mov         ecx,dword ptr [ptr] 
00351D5A 8B 02                mov         eax,dword ptr [edx] 
00351D5C FF D0                call        eax 
00351D5E 3B F4                cmp         esi,esp 
00351D60 E8 48 F5 FF FF       call        __RTC_CheckEsp (03512ADh)
     
    a.AF();
00351D65 8D 4D F0             lea         ecx,[a] 
00351D68 E8 07 F7 FF FF       call        A::AF (0E01474h)       // 普通函数调用,是直接call

6.总结

(1)多态实现的灵魂就是this指针,this指针指向了虚函数表的首地址。this指针决定了调用虚函数时查哪个类的虚函数表。

(2)调用虚函数是间接call,调用普通函数是直接call。

(3)虚函数表首地址位于class的起始地址,这样是为了继承时查表方便。

(4)a = B(); a.AF();不是多态,因为,this传递的是a的,并且是直接call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    A* ptr;
    a = B();
000B1B5F 33 C0                xor         eax,eax 
000B1B61 89 85 FC FE FF FF    mov         dword ptr [ebp-104h],eax 
000B1B67 89 85 00 FF FF FF    mov         dword ptr [ebp-100h],eax 
000B1B6D 89 85 04 FF FF FF    mov         dword ptr [ebp-0FCh],eax 
000B1B73 8D 8D FC FE FF FF    lea         ecx,[ebp-104h] 
000B1B79 E8 C1 F6 FF FF       call        B::B (0B123Fh) 
000B1B7E 50                   push        eax 
000B1B7F 8D 4D F0             lea         ecx,[a] 
000B1B82 E8 F7 F8 FF FF       call        A::operator= (0B147Eh) 
 
    a.AF();     // 运行结果:AF run... a=1
000B1B87 8D 4D F0             lea         ecx,[a] 
000B1B8A E8 E5 F8 FF FF       call        A::AF (0B1474h)   // 直接调用

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-9-3 22:24 被ATrueMan编辑 ,原因: 格式
收藏
点赞6
打赏
分享
最新回复 (3)
雪    币: 19431
活跃值: (29092)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-9-3 23:07
2
1
感谢分享
雪    币: 1064
活跃值: (1000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gupf2024 2024-2-19 11:34
3
0
好文章,谢谢分享
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
shaonianguai 2024-3-13 10:43
4
0
好文章
游客
登录 | 注册 方可回帖
返回