迅雷一道C++面试题!!!求解释

天行者pxhero 2013-10-04 11:42:54
#include <iostream>
using namespace std;

class Base
{
public:
void fun()
{
cout << "Base::fun()" << endl;
}
};

class Derived : public Base
{
public:
virtual void fun()
{
cout << "Derived::fun()" << endl;
}
};

int main()
{
Base *p=new Derived;
p->fun();

delete p;
p=NULL;

return 0;
}

请问各位C++大侠,为什么执行到delete p的时候,运行时崩溃了!!能从对象内存布局角度来解释吗?(个人只知道在执行父类析构函数的时候发生崩溃)
...全文
920 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
unituniverse2 2013-10-10
  • 打赏
  • 举报
回复
怎么这么多人总喜欢提虚函数表?虚函数表并不是标准要求的内容,虽然大部分编译器都使用这种方法实现虚函数
BYSF_XF 2013-10-10
  • 打赏
  • 举报
回复
今天又测试了下,发现在VC++6.0下会崩溃,而在g++下不会。。。
unituniverse2 2013-10-06
  • 打赏
  • 举报
回复
仅这个例子, sizeof(Derived) 等于4, Base * 的位置应该比Derived对象的起始地址多4个字节,其中的Base部分所占大小为0
天行者pxhero 2013-10-06
  • 打赏
  • 举报
回复
引用 22 楼 lcmzgy 的回复:
引用 18 楼 pxhero2012 的回复:
[quote=引用 16 楼 lcmzgy 的回复:] [quote=引用 12 楼 pxhero2012 的回复:] [quote=引用 10 楼 lcmzgy 的回复:] 派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
嗯,3q,有点明白了。但是我搞不懂的一点是,即使是内存泄露,怎么会造成运行时崩溃呢?
这边会崩溃是因为以基类指针来存储来存储派生类的地址和用存储派生类地址,值是不一样的,你上面这篇文章说的是错的http://blog.csdn.net/unituniverse2/article/details/12302139,不信你可以打印出来看看, Derived *sp=new Derived; Base *p=sp; 这边差4,恰好是一个指针的大小。 崩溃的原因是因为detlete p;将调用 全局的void operator delete( void * addr, size_t size),这边穿进去的是基类部分的指针,在函数里面要free(addr),但是我们new 的时候调用的malloc()并不是new出的addr这块内存,而是sp指向派生类的地址,free和malloc不匹配当然会崩溃。http://www.wuzesheng.com/?p=840这边看看就懂了[/quote]我把Derived *sp=new Derived; Base *p=sp;中的p和sp相减,打印出来是0啊,在vc6.0下测试的。不是4。 你给的这篇文章不错,学习了。关于崩溃,同意你的这种解释。3q[/quote] 你不要相减,直接cout出来,[/quote]嗯,确实相差4.cout << ((int)pb-(int)pd) << endl;那Derived对象在内存里的布局是怎样的呢?针对这个题,编译器是vc6.0.
unituniverse2 2013-10-06
  • 打赏
  • 举报
回复
引用 16 楼 lcmzgy 的回复:
引用 12 楼 pxhero2012 的回复:
[quote=引用 10 楼 lcmzgy 的回复:] 派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
嗯,3q,有点明白了。但是我搞不懂的一点是,即使是内存泄露,怎么会造成运行时崩溃呢?
这边会崩溃是因为以基类指针来存储来存储派生类的地址和用存储派生类地址,值是不一样的,你上面这篇文章说的是错的http://blog.csdn.net/unituniverse2/article/details/12302139,不信你可以打印出来看看, Derived *sp=new Derived; Base *p=sp; 这边差4,恰好是一个指针的大小。 崩溃的原因是因为detlete p;将调用 全局的void operator delete( void * addr, size_t size),这边穿进去的是基类部分的指针,在函数里面要free(addr),但是我们new 的时候调用的malloc()并不是new出的addr这块内存,而是sp指向派生类的地址,free和malloc不匹配当然会崩溃。http://www.wuzesheng.com/?p=840这边看看就懂了[/quote] 这文章特意考虑过各种情况,而且说了前提条件。真正的问题是即使是前置基类型的编译器,不见得就会把基类从派生类的基地址开始放,也许会留下一段空缺,后置基类型的编译器也是(那文章写的时候记得还特意注意过措辞,用了“在最好的单继承环境下,可以做到”,“最好的”指的是什么,会不明白吗?还是有意装糊涂?)。而lz给的例子的就在于,基类不含任何的虚函数而派生类有虚函数。但是就是因为基类不含虚析构函数,所以出现通过基类指针无法获得最末端派生类基地址的问题。
unituniverse2 2013-10-06
  • 打赏
  • 举报
回复
引用 18 楼 pxhero2012 的回复:
引用 16 楼 lcmzgy 的回复:
[quote=引用 12 楼 pxhero2012 的回复:] [quote=引用 10 楼 lcmzgy 的回复:] 派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
嗯,3q,有点明白了。但是我搞不懂的一点是,即使是内存泄露,怎么会造成运行时崩溃呢?
这边会崩溃是因为以基类指针来存储来存储派生类的地址和用存储派生类地址,值是不一样的,你上面这篇文章说的是错的http://blog.csdn.net/unituniverse2/article/details/12302139,不信你可以打印出来看看, Derived *sp=new Derived; Base *p=sp; 这边差4,恰好是一个指针的大小。 崩溃的原因是因为detlete p;将调用 全局的void operator delete( void * addr, size_t size),这边穿进去的是基类部分的指针,在函数里面要free(addr),但是我们new 的时候调用的malloc()并不是new出的addr这块内存,而是sp指向派生类的地址,free和malloc不匹配当然会崩溃。http://www.wuzesheng.com/?p=840这边看看就懂了[/quote]我把Derived *sp=new Derived; Base *p=sp;中的p和sp相减,打印出来是0啊,在vc6.0下测试的。不是4。 你给的这篇文章不错,学习了。关于崩溃,同意你的这种解释。3q[/quote] 指针都强制转换转为size_t后再相减就会发现差4
willy1987 2013-10-06
  • 打赏
  • 举报
回复
我猜是因为 基类对象会有一个char类型,但是子类对象因为有了一个虚函数表指针,所以没有char类型,这样问题就出来了,new的时候申请了4个字节,但是delete的时候却delete一个字节。
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 14 楼 Yaoxin_ 的回复:
关于内存泄露前面已经解释得很清楚了。 我在mac osx上测试,运行打印结果正常。 确实不解为何你会执行失败。
那可能是不同的操作系统和不同的编译器对C++的实现,有点不一样。有一些要严格一点。我在windows下的vc6.0下运行,运行到delete时崩溃。但是如果不加delete那句,可以正常打印基类的fun函数,但是加上delete,就会发生运行时崩溃。
有点清眸 2013-10-05
  • 打赏
  • 举报
回复
关于内存泄露前面已经解释得很清楚了。 我在mac osx上测试,运行打印结果正常。 确实不解为何你会执行失败。
beautyangus 2013-10-05
  • 打赏
  • 举报
回复
崩溃应该是Debug版本的问题吧,如果是Release版本的话是不会,Debug会检查每次delete或者free的内存是不是当时new或者malloc返回的内存,并且检查delete/free的内存大小是不是之前new的时候给定的大小。 你这里new的时候是new的D类,所以new的时候的大小就是sizeof(D),但delete的时候是delete的B类,所以,delete的大小就是sizoef(B),二者大小不匹配。 当然,最终分配/释放内存的是malloc/free,只不过在确定内存大小的时候是new和delete,再说直白点,就是构造函数可以确定分配的大小,析构函数可以确定释放的大小,使用D类来分配时正确无疑的,但使用B类指针释放要保证析构函数是虚函数,这样才能正确调用到D类的析构,否则就会导致大小不一致。Release版不会为了这些细枝末节做这么多检测,因为出现问题的是你,而不是运行时。
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 11 楼 unituniverse2 的回复:
[quote=引用 7 楼 pxhero2012 的回复:] [quote=引用 5 楼 unituniverse2 的回复:] 看这个吧。 http://blog.csdn.net/unituniverse2/article/details/12302139
嗯,看了这篇文章有点明白了,但是还有一点不解的是,我这个程序是在vc6.0下运行的,属于文章中所说的第一种编译器(前置基类类型),按文章中所说,不会有任何问题啊!可是实际却有问题![/quote] 我手上已经没有了VC6,没法试了,但是记得早期一些版本的VC,类指针的位置并不是指在对象的开头而是还要加上一个虚表指针的宽度。vc6不知道有没有这种问题。 另外这文章只针对现在的标准和最近的编译器版本。需要强调的只是不该这么用了(就是文章里红字中的内容)。既然是未定义,不如干脆不这么写。[/quote]嗯,你的文章中的红字部分,写得很有道理,以后会谨记这一条的,3q。
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 10 楼 lcmzgy 的回复:
派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
嗯,3q,有点明白了。但是我搞不懂的一点是,即使是内存泄露,怎么会造成运行时崩溃呢?
unituniverse2 2013-10-05
  • 打赏
  • 举报
回复
引用 7 楼 pxhero2012 的回复:
[quote=引用 5 楼 unituniverse2 的回复:] 看这个吧。 http://blog.csdn.net/unituniverse2/article/details/12302139
嗯,看了这篇文章有点明白了,但是还有一点不解的是,我这个程序是在vc6.0下运行的,属于文章中所说的第一种编译器(前置基类类型),按文章中所说,不会有任何问题啊!可是实际却有问题![/quote] 我手上已经没有了VC6,没法试了,但是记得早期一些版本的VC,类指针的位置并不是指在对象的开头而是还要加上一个虚表指针的宽度。vc6不知道有没有这种问题。 另外这文章只针对现在的标准和最近的编译器版本。需要强调的只是不该这么用了(就是文章里红字中的内容)。既然是未定义,不如干脆不这么写。
lcmzgy 2013-10-05
  • 打赏
  • 举报
回复
派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
军说网事 2013-10-05
  • 打赏
  • 举报
回复
崩溃,也有可能是内存被弄乱了,比如数组越界。
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 8 楼 aizibion 的回复:
核心问题是楼主的基类的fun函数并不是虚函数,子类的fun与父类的fun不能进行动态认定。 在主函数中,使用基类指针持有子类对象,在调用fun时,由于fun并不是虚函数,所以只能调用基类fun。 问题出现了,实例化的对象实际是子类,子类对象结构与基类并不一致,此时调用fun函数就成了未定义场景,可能有对象截断:比如截个基类对象出来再调用fun 可能越界访问:比如直接拿着这个对象去访问并基类对象无法控制的区域 或是其他做法 不过在我的cfree5.0上,你这段代码变现还算正常,直接调用了基类fun函数。
嗯,我觉得关于调用基类fun函数,只要只声明一个基类指针就可以调用,不用把基类指针指向任何子类和基类对象。我在VC6.0下运行的时候,崩溃的不是在p->fun(); 而是delete p的 时候,崩溃的。
aizibion 2013-10-05
  • 打赏
  • 举报
回复
核心问题是楼主的基类的fun函数并不是虚函数,子类的fun与父类的fun不能进行动态认定。 在主函数中,使用基类指针持有子类对象,在调用fun时,由于fun并不是虚函数,所以只能调用基类fun。 问题出现了,实例化的对象实际是子类,子类对象结构与基类并不一致,此时调用fun函数就成了未定义场景,可能有对象截断:比如截个基类对象出来再调用fun 可能越界访问:比如直接拿着这个对象去访问并基类对象无法控制的区域 或是其他做法 不过在我的cfree5.0上,你这段代码变现还算正常,直接调用了基类fun函数。
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 5 楼 unituniverse2 的回复:
看这个吧。 http://blog.csdn.net/unituniverse2/article/details/12302139
嗯,看了这篇文章有点明白了,但是还有一点不解的是,我这个程序是在vc6.0下运行的,属于文章中所说的第一种编译器(前置基类类型),按文章中所说,不会有任何问题啊!可是实际却有问题!
lcmzgy 2013-10-05
  • 打赏
  • 举报
回复
引用 18 楼 pxhero2012 的回复:
引用 16 楼 lcmzgy 的回复:
[quote=引用 12 楼 pxhero2012 的回复:] [quote=引用 10 楼 lcmzgy 的回复:] 派生类有虚函数,使用了动态联编,会在类里面多出一个vptr指针来指向虚函数表,以及多出来的一张虚函数表。delete p只是调用基类的析构函数,很明显没有释放派生类里面多出来的内容,造成内存泄露。 这边你可以声明一个虚拟的析构函数,也可以将基类中的fun函数也声明为虚拟的,这样的话,基类中就会也有vptr,和虚函数表,派生类只是继承这些内容。因此在基类的析构就足以将需要释放的资源释放了
嗯,3q,有点明白了。但是我搞不懂的一点是,即使是内存泄露,怎么会造成运行时崩溃呢?
这边会崩溃是因为以基类指针来存储来存储派生类的地址和用存储派生类地址,值是不一样的,你上面这篇文章说的是错的http://blog.csdn.net/unituniverse2/article/details/12302139,不信你可以打印出来看看, Derived *sp=new Derived; Base *p=sp; 这边差4,恰好是一个指针的大小。 崩溃的原因是因为detlete p;将调用 全局的void operator delete( void * addr, size_t size),这边穿进去的是基类部分的指针,在函数里面要free(addr),但是我们new 的时候调用的malloc()并不是new出的addr这块内存,而是sp指向派生类的地址,free和malloc不匹配当然会崩溃。http://www.wuzesheng.com/?p=840这边看看就懂了[/quote]我把Derived *sp=new Derived; Base *p=sp;中的p和sp相减,打印出来是0啊,在vc6.0下测试的。不是4。 你给的这篇文章不错,学习了。关于崩溃,同意你的这种解释。3q[/quote] 你不要相减,直接cout出来,
天行者pxhero 2013-10-05
  • 打赏
  • 举报
回复
引用 1 楼 chenyegui 的回复:
也就是说当我们利用基类指针去释放子类的空间时,如果基类的析构函数不为虚函数,则释放空间时就不会调用子类的析构函数。这时,假如我们在子类的析构函数中存在释放内存的操作,由于不会调用子类的析构函数,因此那块内存也得不到释放,最终造成内存泄露。
嗯,我懂这个道理,但是内存泄露怎么会发生运行时崩溃呢?
加载更多回复(11)

64,648

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧