我们来看一段简单的C++程序。该程序只能在64位机器上正常运行,如果你是32位机器,请自行将main函数内的int64_t都改成int。
如果你能理解全部内容,并且能得出正确的运行结果,说明你对下面这些内容有充分的了解:
- C语言指针本质;
- C语言函数以及函数指针的运用;
- C++对象基本内存模型;
- C++虚函数以及虚函数实现多态原理。
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 <iostream>
class
A
{
virtual
void
a
(
)
=
0
;
virtual
void
b
(
)
=
0
;
}
;
class
A1
:
public
A
{
virtual
void
a
(
)
{
std
::
cout
<<
"A1::a()"
<<
std
::
endl
;
}
virtual
void
b
(
)
{
std
::
cout
<<
"A1::b()"
<<
std
::
endl
;
}
}
;
class
A2
:
public
A
{
virtual
void
a
(
)
{
std
::
cout
<<
"A2::a()"
<<
std
::
endl
;
}
virtual
void
b
(
)
{
std
::
cout
<<
"A2::b()"
<<
std
::
endl
;
}
}
;
typedef
void
(
*
F
)
(
)
;
int
main
(
)
{
A1
a1
;
int64_t*
vptr
=
(
int64_t*
)
(
&
a1
)
;
int64_t*
vtbl
=
(
int64_t*
)
(
*
vptr
)
;
F
a1_f1
=
(
F
)
*
(
vtbl
+
0
)
;
F
a1_f2
=
(
F
)
*
(
vtbl
+
1
)
;
a1_f1
(
)
;
a1_f2
(
)
;
A2
a2
;
vptr
=
(
int64_t*
)
(
&
a2
)
;
vtbl
=
(
int64_t*
)
(
*
vptr
)
;
F
a2_f1
=
(
F
)
*
(
vtbl
+
0
)
;
F
a2_f2
=
(
F
)
*
(
vtbl
+
1
)
;
a2_f1
(
)
;
a2_f2
(
)
;
}
|
上面代码编译运行结果如下:
1
2
3
4
5
|
A1
::
a
(
)
A1
::
b
(
)
A2
::
a
(
)
A2
::
b
(
)
|
关于上述代码的几点解释说明:
-
在C++中,每个对象实例有不同的vtbl(虚函数表);
- 在C++中,一个对象实例的vtbl(虚函数表)指针vptr存储在该对象所在内存起始位置。该指针vptr指向具体的vtable。上述代码第23行给出了如何获取这个指针vptr的方法:取得该对象地址指针,再强制转换成该机器上默认指针长度(比如这里64位机器对应的是int64_t);
-
第24行通过*vptr访问到的具体内容就是vtabl在内存中的起始地址了,vtabl中保存着该对象虚函数地址,我将其强制转换成当前机器CPU位数相同的指针类型后就可以通过简单的加减访问到vtbl所有函数的地址;
-
通过在内存中偷来的函数指针,我们就可以直接去调用相应的函数。参见第25行和26的代码,我们只要将该指针转成相应的函数指针就可以顺利调用。
上述内存模型示意如下:
版权声明
本博客所有文章皆为原创,作者保留所有版权。转载必须保证全文完整和包含本声明,并以超链接形式注明出处http://www.macode.net/c-vptr-vtbl/