Chinaunix

标题: delete一个对象数组的问题,请大侠帮忙看看 [打印本页]

作者: happyness    时间: 2007-01-12 12:40
标题: delete一个对象数组的问题,请大侠帮忙看看
下面这段代码,在Linux下用g++编译后运行时core dump。
请各位大侠看看是什么原因,多谢!


  1. #include <iostream>

  2. class Base
  3. {
  4. public:
  5.         Base () {}
  6.         virtual ~Base () {}
  7. };

  8. class Derived: public Base
  9. {
  10. public:
  11.         Derived (): i_(10) {}
  12.         virtual ~Derived () {}
  13. private:
  14.         int i_;
  15. };

  16. int main()
  17. {
  18.         Base *p = new Derived[2];
  19.         delete[] p;
  20.        
  21.         return 0;
  22. }
复制代码

作者: lnfxcf    时间: 2007-01-12 15:15
up!
我也想只到为什么?
作者: yulc    时间: 2007-01-12 16:14
Base *p = new Derived[2];

可能是编译器在new数组时,隐含了计算new出来数组大小的工作.
这样强制转换后,由于子类比基类多出一个int字节, 所以在delete[]时出错了.

[ 本帖最后由 yulc 于 2007-1-12 16:18 编辑 ]
作者: lnfxcf    时间: 2007-01-12 17:45
原帖由 yulc 于 2007-1-12 16:14 发表
Base *p = new Derived[2];

可能是编译器在new数组时,隐含了计算new出来数组大小的工作.
这样强制转换后,由于子类比基类多出一个int字节, 所以在delete[]时出错了.

那为什么delete p;就没有问题?
作者: tyc611    时间: 2007-01-12 17:45
原帖由 happyness 于 2007-1-12 12:40 发表
下面这段代码,在Linux下用g++编译后运行时core dump。
请各位大侠看看是什么原因,多谢!

[code]
#include <iostream>

class Base
{
public:
        Base () {}
        virtual ~Base () {}
};

class  ...
In the second alternative (delete array) if the dynamic type of the
object to be deleted differs from its static type, the behavior is undefined.73)
               --------ISO/IEC 14882:1998 5.35

程序中对象的static type为Base,而dynamic type为Derived
类型不一致,产生未定义行为

可以这样做:delete [] (Derived*)p;
不过,这样不是很好的处理方式,因为必须知道它的真实类型
作者: tyc611    时间: 2007-01-12 17:47
原帖由 lnfxcf 于 2007-1-12 17:45 发表

那为什么delete p;就没有问题?
In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the
static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual
destructor or the behavior is undefined.
                  --------ISO/IEC 14882:1998 5.35

作者: yulc    时间: 2007-01-16 10:56
原帖由 lnfxcf 于 2007-1-12 17:45 发表

那为什么delete p;就没有问题?



这个问题是根据自己的想法回复的, 不能以理服人.hehe
昨晚无意中翻开more effective c++, 找到了大师的说法:




3.3 Item M3:不要对数组使用多态
类继承的最重要的特性是你可以通过基类指针或引用来操作派生类。这样的指针或引用具有行为的多态性,就好像它们同时具有多种形态。C++允许你通过基类指针和引用来操作派生类数组。不过这根本就不是一个特性,因为这样的代码几乎从不如你所愿地那样运行。

假设你有一个类BST(比如是搜索树对象)和继承自BST类的派生类BalancedBST:
class BST { ... };
class BalancedBST: public BST { ... };

在一个真实的程序里,这样的类应该是模板类,但是在这个例子里并不重要,加上模板只会使得代码更难阅读。为了便于讨论,我们假设BST和BalancedBST只包含int类型数据。
有这样一个函数,它能打印出BST类数组中每一个BST对象的内容:

  1. void printBSTArray(ostream& s,
  2. const BST array[],
  3. int numElements)
  4. {
  5.   for (int i = 0; i < numElements; ) {
  6.     s << array[i]; //假设BST类重载了操作符 < <
  7.   }
  8. }
复制代码
当你传递给该函数一个含有BST对象的数组变量时,它能够正常运行:
BST BSTArray[10];
...
printBSTArray(cout, BSTArray, 10); // 运行正常
然而,请考虑一下,当你把含有BalancedBST对象的数组变量传递给printBSTArray函数时,会产生什么样的后果:
  1. BalancedBST bBSTArray[10];
  2. ...
  3. printBSTArray(cout, bBSTArray, 10); // 还会运行正常么?
复制代码


你的编译器将会毫无警告地编译这个函数,但是再看一下这个函数的循环代码:
  1. for (int i = 0; i < numElements; ) {
  2.   s << array[i];
  3. }
复制代码



这里的array[I]只是一个指针算法的缩写:它所代表的是*(array)。我们知道array是一个指向数组起始地址的指针,但是array中各元素内存地址与数组的起始地址的间隔究竟有多大呢?它们的间隔是i*sizeof(一个在数组里的对象),因为在array数组[0]到[I]间有I个对象。编译器为了建立正确遍历数组的执行代码,它必须能够确定数组中对象的大小,这对编译器来说是很容易做到的。参数array被声明为BST类型,所以array数组中每一个元素都是BST类型,因此每个元素与数组起始地址的间隔是i*sizeof(BST)。

至少你的编译器是这么认为的。但是如果你把一个含有BalancedBST对象的数组变量传递给printBSTArray函数,你的编译器就会犯错误。在这种情况下,编译器原先已经假设数组中元素与BST对象的大小一致,但是现在数组中每一个对象大小却与BalancedBST一致。派生类的长度通常都比基类要长。我们料想BalancedBST对象长度的比BST长。如果如此的话,printBSTArray函数生成的指针算法将是错误的,没有人知道如果用BalancedBST数组来执行printBSTArray函数将会发生什么样的后果。不论是什么后果都是令人不愉快的。


如果你试图删除一个含有派生类对象的数组,将会发生各种各样的问题。以下是一种你可能采用的但不正确的做法。

//删除一个数组, 但是首先记录一个删除信息
  1. void deleteArray(ostream& logStream, BST array[])
  2. {
  3.   logStream << "Deleting array at address "
  4.     << static_cast<void*>(array) << '\n';
  5.   delete [] array;
  6. }
  7. BalancedBST *balTreeArray = // 建立一个BalancedBST对象数组
  8. new BalancedBST[50];
  9. ...
  10. deleteArray(cout, balTreeArray); // 记录这个删除操作
复制代码


这里面也掩藏着你看不到的指针算法。当一个数组被删除时,每一个数组元素的析构函数也会被调用。当编译器遇到这样的代码:
delete [] array;
它肯定象这样生成代码:
  1. // 以与构造顺序相反的顺序来
  2. // 解构array数组里的对象

  3. for ( int i = 数组元素的个数 1; i >= 0;--i)
  4. {
  5. array[i].BST::~BST(); // 调用 array[i]的
  6. } // 析构函数
复制代码


因为你所编写的循环语句根本不能正确运行,所以当编译成可执行代码后,也不可能正常运行。语言规范中说通过一个基类指针来删除一个含有派生类对象的数组,结果将是不确定的。这实际意味着执行这样的代码肯定不会有什么好结果。多态和指针算法不能混合在一起来用,所以数组与多态也不能用在一起。
值得注意的是如果你不从一个具体类(concrete classes)(例如BST)派生出另一个具体类(例如BalancedBST),那么你就不太可能犯这种使用多态性数组的错误。正如条款M33所解释的,不从具体类派生出具体类有很多好处。我希望你阅读一下条款M33的内容。

[ 本帖最后由 yulc 于 2007-1-16 11:00 编辑 ]
作者: redac    时间: 2007-01-29 15:59
一个有意义的问题,顶!!!
作者: westgarden    时间: 2007-01-29 18:19
解决办法:
在class Base中重新定义
operator new[]和operator delete[]。

具体请参TC++PL 15.6节。
作者: converse    时间: 2007-01-29 18:22
问题加精,感谢几位的精彩回复.
作者: blackuhlan    时间: 2007-01-30 14:02
所谓c++的切片问题就是这种情况,可以作为一个经典案例.java就没有这个问题.解决办法就是把base扩展为和derived一样的属性和方法,不需要的方法改为virtual
作者: broadoceans    时间: 2007-01-30 19:00
标题: 如果上面的有问题,就是编译器的错误了。
上面的例子很好,这就是为什么我们需要将析构函数定义为虚函数的原因。

楼主的例子很好,如果有错,就是编译器的问题了。

实际上,delete[] p和delete p 的区别仅仅是调用析构函数的次数不一样,
像你的例子中,两者效果是一样的。

如果类中有资源需要释放,就必须用delete[] ,以便多次(2次)调用析构函数。

delete p总能把对象本身的内存释放掉,因为分配时有记录。
作者: 清汤挂面    时间: 2007-01-30 20:58
呵呵,这个是因为在做数组析构的时候,编译器会对每个元素都做一次析构,由于代码中写的是基类的指针,因此,编译器在计算每个元素的偏移的时候,是按照基类的大小来计算的。因此,第一个元素的析构函数可以被正常调用,而第二个是不可以的,因为此时已经指向了错误的内存区域。如果基类大小和子类大小是一样的,那么这段代码就不会core掉。

总之,不要将动态的特性施行于数组之上。
作者: shanan    时间: 2007-03-14 09:53
理解了more effective c++中说的就不会这样写代码了
作者: xinxin12375    时间: 2009-08-29 15:39
原帖由 清汤挂面 于 2007-1-30 20:58 发表
呵呵,这个是因为在做数组析构的时候,编译器会对每个元素都做一次析构,由于代码中写的是基类的指针,因此,编译器在计算每个元素的偏移的时候,是按照基类的大小来计算的。因此,第一个元素的析构函数可以被正 ...


解释得非常好!
作者: beijibing2010    时间: 2011-12-31 14:33
可以这样删除:
delete[] (Derived*)p;

改造两个析构函数
  1. virtual ~Base () {std::cout<<"~Base ()"<<std::endl;}

  2. virtual ~Derived () {std::cout<<"~Derived ()"<<std::endl;}
复制代码
输出结果如下:
  1. ~Derived ()
  2. ~Base ()
  3. ~Derived ()
  4. ~Base ()
复制代码

作者: InMySin    时间: 2012-08-20 09:33
哎呦,我也用到了对象数组的问题,但是使用继承类的不多,不过这个隐含问题不简单啊
作者: 待到天明    时间: 2013-03-12 23:46
厉害,在此受教了^_^
作者: cokeboL    时间: 2013-03-13 09:25
就应该废掉C++这种语言

开发者应该专注于算法、结构、实现

而想成为C++的高手,首要任务竟然是让自己了解C++的数不清的坑,这通常要耗费大量时间

个人观点,不喜勿喷,懒得争吵
作者: cokeboL    时间: 2013-03-13 11:25
回复 15# xinxin12375


are you sure?

那只是对一种编译器行为的解释,语言规范中说通过一个基类指针来删除一个含有派生类对象的数组,结果将是不确定的,
我用VC就没挂,也就是说编译器厂商的实现是不一样的

所以13楼的回答中最可取的就一句:总之,不要将动态的特性施行于数组之上。
作者: thaldn    时间: 2013-04-09 20:24
不知道新标准有没有define这个问题
作者: hu_lu_wa    时间: 2013-09-16 10:05



回复 4# lnfxcf


    你运行过没 就说 delete p 没问题


作者: zhangge3663    时间: 2014-03-12 13:41
把那个virtual去掉后,就不会出现段错误了!
作者: havent2012    时间: 2014-05-11 16:39
受教了!不错的
作者: neodreamerus    时间: 2016-07-27 15:49
不要对数组使用多态。学习10年前的老贴。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2