首先要说的是本文所说的缺陷并非指指针悬空或野指针造成的程序崩溃,我觉得那些问题只要小心的处理,是可以避免的。本文所说的缺陷是指指针对C++对象封装的破坏。
想到写这个话题是因为看到有人问了一个问题“基类的私有成员会不会被子类继承?”,我认为是会的,因为C++标准保证“出现在子类中的基类子对象要保持其原样完整性”。于是我就写了一段代码来验证我的想法,如下:
class Base { public: Base(): m_data1(123) { } // 赋值为123方便观察 private: int m_data1; }; class Derived: public Base { public: Derived(): Base() { } public: int m_data2; };
输出sizeof(Derived)为8,也就是说基类的私有成员m_data1也被继承了,它存在于子类对象中。由此我想到既然子类有基类的完整对象,那么根据子类的指针也是可以访问基类private域里的成员。Derived的内存布局如下:
既然我们知道了成员变量的内存布局,那么我们就可以用指针来获取父类的成员变量,甚至赋值,从而绕过语法对成员变量封装的保护。方法如下:
Derived* pd = new Derived(); cout<<*((int*)pd) <<endl; // 输出123 *((int*)pd) = 7; cout<<*((int*)pd) <<endl; // 输出7
这种办法对于基类的私有虚函数也是一样可以这样绕过的,直接调用。我们定义如下的类:
class Base { private: virtual void f1() { cout<<"Base function" <<endl; } }; class Derived: public Base { public: virtual void f2() { cout<<"Derived function" <<endl; } };
从上面的代码我们可以画出类的内存图,如下:
根据对象的内存布局我们就可以直接通过Derived对象指针调用Base对象的private类型的虚函数,方法如下:
int main() { typedef void (*fun)(); Derived* pd = new Derived(); fun f = (fun)*((int*)*(int*)pd); f(); // 输出:Base function return 0; }
由于我们在创建对象的时候能获取对象的地址,而C++的对象模型我们是知道的,那么我们就可以通过C++的指针绕过对成员变量的保护,但这对于非虚函数是不成立的。