一. 示例代码
Father.h
#ifndef TEST1__FATHER_H_
#define TEST1__FATHER_H_
class Father {
public:
virtual void func_1();
virtual void func_2();
virtual void func_3();
private:
int x = 1;
int y = 2;
};
#endif //TEST1__FATHER_H_
Father.cpp
#include "Father.h"
#include <iostream>
using namespace std;
void Father::func_1() {
cout << __FUNCTION__ << endl;
}
void Father::func_2() {
cout << __FUNCTION__ << endl;
}
void Father::func_3() {
cout << __FUNCTION__ << endl;
}
二. 对应的对象模型
因为Father类中含有虚函数, 所以Father类所构造的对象中含有一个虚函数的指针, 这个指针指向虚函数表, 其次是定义的成员变量x, y.
三. 验证模型的正确性
我们需要将father对象的前面8个字节取出来
方法如下:
long long *vptr = (long long *)*(long long *)(&father);
&father(对father对象取地址时), 得到一个地址值, 这个地址值指向对象的首地址, 代表了长度为16字节的内存单元, 我们现在需要取内存的前面8个字节, 所以我们将Father *类型强制转换为long long *类型(long long类型一般都为8个字节), 经过这一步操作后, 再进行一次解引用, 即*(long long *)(&father)后, 得到了虚函数表中func1的实际地址. 解引用之后, 得到的指针并非为long long类型, 所以此时我们将解引后的地址强制转换为long long类型, 即上面所写代码.
至此, 我们已经获得了一个long long类型的地址, 但是这个地址并不能执行函数, 当然是这样, 在64位机器中, 指针的长度为8个字节, 不管什么类型的指针(包括void *)都为8个字节, 计算机是通过指针的类型才可以完成相应的操作, 所以我们需要将我们获得的vptr转换成void (*p)(void)这样的函数指针才可以执行对应的虚函数.(ps.如果是32位机, 指针为4字节, 使用int替换long long)
test.cpp
#include "Father.h"
#include <iostream>
typedef void (*fun_c)(void);
using namespace std;
int main() {
Father father;
long long *vptr = (long long *)*(long long *)(&father);
for (int i = 0; i < 3; i++) {
((fun_c)(*vptr++))();
}
return 0;
}
接下来, 我们需要访问x, y两个元素(通过father对象并不可以访问, 因为成员变量为private)
方法如下:
#include "Father.h"
#include <iostream>
using namespace std;
int main() {
Father father;
for (int i = 0; i < 2; i++) {
cout << *(int *)((long)(&father) + 8 + 4 * i) << endl;
}
return 0;
}
先将father对象的地址转化为一个long类型的数字, +8字节跳过虚函数表指针, (4 * i) 当第一轮输出时,i为0, 输出刚好为x的值, 第二轮循环时, +4个字节, 此时刚好输出y的值.
结果为: