C++ 逐步解析 (func)*((int*)*(int*)(&d)) 如何获取虚函数表以及虚函数地址

虚函数的作用、原理等具体内容可以看我的另一篇文章 C++ 虚函数详解
这里讨论如何通过对象找到他的虚函数表还有相应的虚函数。

相关概念:
变量:不管是用int还是int*声明的变量,本质上都是一样的,都存储在内存中,都具有两个要素:地址内容
地址:指变量存储在内存中的地址,一般用16进制表示,如0x61febc。可以用 & 对一个变量进行取址,返回的就是变量地址。
内容:指存储在内存中的内容,如char大小为一个字节,那么他就可以表示2的8次方个字符。(ASCII码决定了什么数位表示什么字符)。
指针:指针和普通变量“不一样”的地方在于指针存储的内容是“地址”。因此可以对指针进行解引用(*)操作,就会返回存储在这个地址的变量的值。不同类型的指针,存储的都是地址,他们的差别在于解引用(*)的时候根据不同类型读取长度不同的内容,int*类型会从地址开始读取四个字节的内容,而long long*会从地址开始读取八个字节的内容。

用指针获取虚函数表指针和虚函数表内容:

class Drive{
public:
    virtual void g(){
        cout << "Drive g()" << endl;
    }
    virtual void h(){
        cout << "Drive h()" << endl;
    }
    int m_Drive = 9;

};

int main(){

    typedef void (*func)(void);
    func f;

    Drive* d = new Drive();
	//找到虚函数表并执行其中的虚函数
    int* pDrive = (int*)*(int*)d;
    f = (func)*(pDrive + 0);
    f();
    f = (func)*(pDrive + 1);
    f();
	//通过内存访问成员变量
    pDrive = (int*)d + 1;
    cout << *pDrive << endl;

    return 0;
}
//输出
Drive g()
Drive h()
9

访问虚函数解析:

1、Drive* d = new Drive(); 变量d是一个指针,存储的是对象的地址。

2、(int*)d; 目标是求出虚函数表指针。虚函数表指针存储在对象的起始地址,并且函数指针大小为四个字节,所以可以转化为整型指针 int*,这样在对(int*)d解引用(*)的时候,可以正确获得虚函数表指针。

指针的类型,指的是这个地址上存储的变量的类型。使用正确的指针类型,在对指针解引用或者加减操作的时候,才能有正确的表现。

3、*(int*)d; 解引用,此时获得虚函数表的地址(虚函数表中顺序存储着所有虚函数的地址(函数指针))。

4、int* pDrive = (int*)*(int*)d; 要通过虚函数表地址获得他的内容,需要解引用(*),要先转化成指针类型,因为虚函数表的内容是函数指针(四个字节大小),所以转化成整型指针。

5、f = (func)*(pDrive + 0) 解引用获得第一个虚函数的地址,再转化成对应的(func)函数形式,供调用。

6、f()根据函数地址,调用函数。

7、*(pDrive + 1) 指针 + 1,因为是整型指针,向下移动四个字节,也就是第二个虚函数地址的位置。重复5、6就可以调用函数。

8.pDrive = (int*)d + 1; 在对象实例的内存中,虚函数指针在首四个字节,成员变量排在他的后面,所以先把d转成整形指针,再+1,指针就会向下移动四个字节,直接解引用*pDrive就可求出变量。


拓展:

如果成员变量由 int 变为long long,那么要怎么得出来呢?我想到的是:

//答案A
    int* pDrive = (int*)d + 1;		//(1)

    cout << *(long long*)pDrive << endl;	//(2)

因为通过操作(1),指针已经指向第四个字节的位置,而这就是成员变量的起始位置(我认为),变量大小为八个字节,所以先转成long long* 再解引用就可以得出答案。但是事实并非如此。

解答:
gcc编译默认内存对齐单位是4,也就是#pragma pack(4)
但是下面这种情况:

class T{
public:
	int n;
	long long  m;
};
//sizeof(T) = 16

如果内存对齐单位为4的话,大小应该为12而不是16,这反而像是内存对齐为 8,我也不知道为什么TAT,可能是编译器自己的优化?
在显式设定#pragma pack(4)后,结构体大小是12,符合内存对齐单位为4了,这时,答案A可用的。

内存对齐作用、规则相关知识

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值