今天被一个派生类指针内存越界访问的问题纠缠了半边,还是学艺未精啊,赶紧总结一下…
首先,有这么两个类:
class A
{
public:
virtual ~A();
};
class B:public A
{
public:
void SetValue(int val){m_ivalue = val;}
int GetValue(){return m_ivalue;}
private:
int m_ivalue;
};
指针经过下行转换,然后访问派生类成员(内存越界):
A* pa = new A();
B* pb = static_cast<B*>(pa);
pb->SetValue(1234);
cout<< "The value is :" << pb->GetValue()<<endl;
运行结果却是。。。正常运行!!
这就尴尬了。。pb指向的内存是通过new A申请的,怎么还有m_ivalue的事?
仔细琢磨了一下,其实也不是很难理解,只要理清楚内存分布:
A的大小是4byte(只有一个虚函数表),new A申请到的堆内存大小自然也是4byte,而B的大小是8byte,
B的内存分布是这样的:
0-4byte: B的虚函数表
5-8byte:m_ivalue
而上例中pb指向的堆内存分布是这样子的:
0-4byte:A的虚函数表
大于4byte:未分配或者被用于其他"申请者"
当执行pb->SetValue(1234);语句时,其实是往pb偏移4byte的地址写入1234,但是从上面可以看到,这部分内存是没有经过申请的,难道也可以直接使用吗?
其实是可以的。
我将main函数改成如下:
A* pa = new A();
B* pb = static_cast<B*>(pa);
pb->SetValue(1234);
int *pi = (int*)pb;
pi += sizeof(A)/sizeof(int);//偏移4个字节
*pi = 4321;
cout<< "\r\n The value is :" << pb->GetValue()<<endl;
这里定义一个int类型的指针,直接操作该内存。
运行结果:
可以看到,值4321被成功写入,说明没有申请的堆内存,其实是可读可写的。
我也想不明白为什么要这么设定,这样导致的后果就是看起来程序没有任何问题,但是指不定什么时候,就会操作到“别人”申请的内存,调试也麻烦得很。