• 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 };
    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)

    结果和预期一样,证明自己的分析是符合事实的。

  • 相关阅读:
    Django(一)
    web 框架
    图片
    day16
    day 15
    day14 HTML CSS
    day12
    day11
    python IO多路复用,初识多线程
    python socket
  • 原文地址:https://www.cnblogs.com/lanyuliuyun/p/3052938.html
Copyright © 2020-2023  润新知