C++ 虚指针、成员变量与类对象的偏移地址

先给出一段代码实现

#include <iostream>
using namespace std;
class animal
{
protected:
    int age;
public:
    virtual void print_age(void) = 0;
};
class dog : public animal
{
public:
       dog() {this -> age = 2;}
       ~dog() { }
       virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
};
class cat: public animal
{
public:
    cat() {this -> age = 1;}
    ~cat() { }
    virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
};
int main(void)
{
     cat kitty;
     dog jd;
     animal * pa;
     int * p = (int *)(&kitty);
     int * q = (int *)(&jd);
    p[0] = q[0];
    pa = &kitty;
    pa -> print_age();
    return 0;
}
【源码输出】
代码输出是 Wang, my age = 1;
如果将 p[0] = q[0]; 换为 p[1] = q[1]; 则输出为 Miao, my age = 2。

【源码分析】

首先,这是一个取巧的改变虚表指针的办法,它利用了C++的对象模型的特点。我们知道,一个类有了虚函数后,它会有一个虚表来维护虚函数和一个虚表指针__vptr来指向它,而这个程序利用的即是改变虚指针的指向。它首先&kitty,并且转换为int*,获得cat类的虚表首地址,同样&jd获得dog类的虚表地址,而p[0] = q[0]令指向cat的虚表首地址,一下就变成了指向dog类的虚表首地址,然后基类获取到了这个指向dog类的kitty,调用虚方法则自然调用到了dog的print_age,然后这里的age则依然保留的是cat的,因为你只是改变了虚指针指向的虚表地址,不影响member data。
重中之重,记住一个点:类对象的首地址是虚函数指针地址,其次是变量地址;改变对象指针类型,将改变实函数,改变对象指针变量,将改变虚函数与成员变量。
其次,这代码不但依赖某些C++编译器的行为,还依赖平台的指针宽度是32位。
int * p = (int *)(&kitty);
int * q = (int *)(&jd);
p[0] = q[0];
这几句不应该用int*,而应该用intptr_t*才对。这样才能保证拷贝的是一个指针宽度的数据,而不是一个int宽度的数据。
在32位平台上,int通常是32位,而指针是32位,所以正好匹配了,程序能正常运行;
在64位平台上,如果是流行的LP64模型,int是32位而指针是64位,这里实际上只拷贝了指针的一半,程序能否正常运行就看运气了。
如果是在一个64位且小端(little endian)的平台上,那这代码拷贝的是指针的低32位。很可能会运气好能正常运行,因为dog类与cat类的vtable可能正好在内存里处于很近的位置,它们的地址的高32位可能正好相同,地址不同的地方都在低32位,这样这个程序就运气好能正常运行。
如果是在一个64位且大端(big endian)的平台上,那这段代码拷贝的是指针的高32位,那就完全达不到效果了。
最后,这种题还有很多玩法。例如说一种简单的玩法是像这样:

#include <iostream>
using namespace std;
class animal
{
protected:
    int age;
public:
    virtual void print_age(void) = 0;
};
class dog : public animal
{
public:
       dog() {this -> age = 2;}
       ~dog() { }
       virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
};
class cat: public animal
{
public:
    cat() {this -> age = 1;}
    ~cat() { }
    virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
};
int main(void)
{
     cat kitty;
     dog jd;
     animal * pa;
     int * p = (int *)(&kitty);
     int * q = (int *)(&jd);
    p[0] = q[0];
    pa = &kitty;
    pa -> print_age();
    return 0;
}
直接整个vtable伪造出来然后想往里面填啥就填啥。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值