首先这是从酷壳网看到陈皓和云风两位大牛们讨论的一个问题,这里记录一下自己的理解。问题如下,先看一段代码
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public : 7 Base(){} 8 virtual ~Base(){ 9 cout<<"Base::~Base()"<<endl; 10 } 11 }; 12 13 class Derived : public Base 14 { 15 public: 16 Derived(){} 17 virtual ~Derived(){ 18 cout<<"Derived::~Derived()"<<endl; 19 } 20 21 int member; 22 }; 23 24 int main() 25 { 26 Derived *pd = new Derived[3]; 27 Base *pb = new Derived[3]; 28 29 delete[] pd; 30 delete[] pb; 31 32 return 0; 33 }
这段代码是不能正常工作的,在执行第30行时将内存出错,如下
Administrator@attention /e/Code $ g++ -g Poly.cpp -o poly -lstdc++ Administrator@attention /e/Code $ gdb -q poly.exe Reading symbols from e:\Code\poly.exe...done. (gdb) b 30 Breakpoint 1 at 0x401453: file Poly.cpp, line 30. (gdb) r Starting program: e:\Code\poly.exe [New Thread 5132.0x8b0] Derived::~Derived() Base::~Base() Derived::~Derived() Base::~Base() Derived::~Derived() Base::~Base() Breakpoint 1, main () at Poly.cpp:30 30 delete[] pb; (gdb) n Derived::~Derived() Base::~Base() Program received signal SIGSEGV, Segmentation fault. 0x00401477 in main () at Poly.cpp:30 30 delete[] pb; (gdb)
可乍一看,也没什么错啊!利用基类指针操作子类对象,通常的多态不就是这样做的吗?但这里出错,不是多态的问题,而是内存管理的问题,而且还是C语言内存管理的问题。具体原因还得从C++对象实例的成员结构说起。含有虚函数的C++对象实例,是利用虚函数表来实现多态的,在继承体系中,动态的将虚函数表中的指针修改为具体类虚函数成员的地址,来实现利用基类指针调用子类不同实现方法的目的,即多态。为了达到这一目的,C++编译器将对象中虚函数表的指针放在对象的首部,之后才是各个成员变量。那么用基类指针指向子类对象时,虚函数表就可以直接相互对应上。具体请看
(gdb) p *pd $1 = {<Base> = {_vptr.Base = 0x452b98}, member = -17891602} (gdb) p *pb $2 = {_vptr.Base = 0x403180}
好了了解这些之后,再回到刚才的问题,为什么会出现内存出错呢?仔细看一下gdb,单步跟踪30行执行n之后,是有打印一次"Derived::~Derived()"和"Base::~Base()"的,紧接着就出现SIGSEGV了,可见动态分配的数组中第一个成员的操作结果是正常的,那么显然是从第二个成员开始出错的。现在回想一下,在C/C++数组中,如何从上一个数组成员偏移到下一个数组成员呢?简单是将当前成员的地址+成员的尺寸,操作方法简单是将当指向上一个元素的指针进行++操作,注意这里我着重将计算方法的两个元素高亮来,上面代码出错也就是在这里。利用基类指针指向一个子类对象数组的起始地址之后,若直接利用基类指针做++操作,“成员的尺寸”使用的是基类的尺寸,而不是子类的,那么刚才的代码中,在操作第一个成员之后,意图进行偏移指向第二个成员时,由于基类和子类尺寸不相同,“成员的尺寸”不正确,而导致操作之后,基类指针不再正确的指向第二个元素了,那么虚函数表的指向也就是错误的,再进行析构函数调用,那么必然出错。
上面这些分析,是自己对陈皓大牛博文中分析过程的理解,叙述的可能不太准确。为此,我额外写了一段代码验证来一把
1 #include <iostream> 2 using namespace std; 3 4 class Base 5 { 6 public : 7 Base(){} 8 virtual ~Base(){ 9 cout<<"Base::~Base()"<<endl; 10 } 11 virtual void Method(void){ 12 cout<<"Base::Mehod"<<endl; 13 } 14 }; 15 16 class Derived : public Base 17 { 18 public: 19 Derived(){} 20 virtual ~Derived(){ 21 cout<<"Derived::~Derived()"<<endl; 22 } 23 virtual void Method(void){ 24 cout<<"Derived::Mehod"<<endl; 25 } 26 27 int member; 28 }; 29 30 int main(int argc, char *argv[]) 31 { 32 Derived *pd = new Derived[3]; 33 Base *pb = pd; 34 35 pb++; 36 pb->Method(); 37 38 delete[] pd; 39 40 return 0; 41 }
按照上面的分析,该段代码将预期在36行出错,实际GDB跟踪如下
Administrator@attention /e/Code $ g++ -g poly2.cpp -o poly2 -lstdc++ Administrator@attention /e/Code $ ./poly2.exe Administrator@attention /e/Code $ gdb -q poly2.exe Reading symbols from e:\Code\poly2.exe...done. (gdb) b 35 Breakpoint 1 at 0x4013e2: file poly2.cpp, line 35. (gdb) r Starting program: e:\Code\poly2.exe [New Thread 4212.0x13cc] Breakpoint 1, main (argc=1, argv=0x2f2bc0) at poly2.cpp:35 warning: Source file is more recent than executable. 35 pb++; (gdb) n 36 pb->Method(); (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x004013f0 in main (argc=1, argv=0x2f2bc0) at poly2.cpp:36 36 pb->Method(); (gdb)
结果和预期一样,证明自己的分析是符合事实的。