C++虚函数、this、多态

虚函数

Virutal关键字修饰的成员函数为虚函数,虚函数的作用是实现多态。多态是将接口与实现分开的技术。

虚函数表

虚函数表位于类的起始地址,通过虚函数表可实现函数的间接调用,若子类需要重写父类的方法,则将虚函数表内的函数地址修改即可。通过this指针可以检索到虚函数表的位置。

image-20240316131459287

无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...

类初始化之前,会将需要初始化的类地址传递给ecx寄存器。实际该值为this指针。

image-20240316131810042

经过构造器之后,该地址值会被填充为虚函数地址表。

image-20240316132155233

那么想要通过虚函数表调用类函数则需要先取出类地址->取出虚函数表地址->取出函数地址

image-20240316132508059

修复this指针

(1) 即使没有显示的调用this,在汇编中也会隐式的传递this指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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);
}

在使用类变量引用函数时,无需间接调用函数,而是直接调用。

image-20240316133519310

(2)通过传递this指针输出变量a的值,可以看到在AF函数中输出变量的a的值依靠this指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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();
}

若不显示传递this指针,则没办法定位的a的位置

image-20240316134459699

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

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

要想要获取B类中的a的值,就需要获取B类的this指针。

image-20240316134903243

这里需要注意的是,不能直接将B类的地址作为this指针传入,这样获取的类A的this指针,这是因为类B是继承类A的,在内存中,会首先存储类A的结构,接着才是类B的。因此B类的this指针实际是指向类A。

image-20240316135042488

虽然类B是继承类A,但是类B与类A的虚函数地址是不一致的,那么多态的实现实际是修改了虚函数地址表中函数的地址。

若类B继承类A,但是没有重写方法。虚函数地址表会改变。如下图

image-20240316135736240

函数地址未改变,因此若是多态的话,这函数地址会改变。

image-20240316135851744

参考链接

https://bbs.kanxue.com/thread-278697.htm%EF%BC%89


C++虚函数、this、多态
https://h0pe-ay.github.io/C++虚函数、this、多态/
作者
hope
发布于
2024年3月16日
许可协议