专职C++

不能停止的脚步

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  163 Posts :: 7 Stories :: 135 Comments :: 0 Trackbacks

常用链接

留言簿(28)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

原文地址:http://coolshell.cn/articles/9543.html

先是在微博上看到了个微博和云风的评论,然后我回了“楼主对C的内存管理不了解”。

后来引发了很多人的讨论,大量的人又借机来黑C++,比如:

//@Baidu-ThursdayWang:这不就c++弱爆了的地方吗,需要记忆太多东西

//@编程浪子张发财:这个跟C关系真不大。不过我得验证一下,感觉真的不应该是这样的。如果基类的析构这种情况不能 调用,就太弱了。

//@程序元:现在看来,当初由于毅力不够而没有深入纠缠c++语言特性的各种犄角旮旯的坑爹细枝末节,实是幸事。为现在还沉浸于这些诡异特性并乐此不疲的同志们感到忧伤。

然后,也出现了一些乱七八糟的理解:

//@BA5BO: 数组是基于拷贝的,而多态是基于指针的,派生类赋值给基类数组只是拷贝复制了一个基类新对象,当然不需要派生类析构函数

//@编程浪子张发财:我突然理解是怎么回事了,这种情况下数组中各元素都是等长结构体,类型必须一致,的确没法多态。这跟C#和java不同。后两者对于引用类型存放的是对象指针。

等等,看来我必需要写一篇博客以正视听了。

因为没有看到上下文,我就猜测讨论的可能会是下面这两种情况之一:

1) 一个Base*[]的指针数组中,存放了一堆派生类的指针,这样,你delete [] pBase; 只是把指针数组给删除了,并没有删除指针所指向的对象。这个是最基础的C的问题。你先得for这个指针数组,把数据里的对象都delete掉,然后再删除数组。很明显,这和C++没有什么关系。

2)第二种可能是:Base *pBase = new Derived[n] 这样的情况。这种情况下,delete[] pBase 明显不会调用虚析构函数(当然,这并不一定,我后面会说) ,这就是上面云风回的微博。对此,我觉得如果是这个样子,这个程序员完全没有搞懂C语言中的指针和数组是怎么一回事,也没有搞清楚, 什么是对象,什么是对象的指针和引用,这完全就是C语言没有学好。

后来,在看到了 @GeniusVczh 的原文 《如何设计一门语言(一)——什么是坑(a)》最后时,才知道了说的是第二种情况。也就是下面的这个示例(我加了虚的析构函数这样方便编译):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base
{
  public:
    virtual ~B(){ cout <<"B::~B()"<<endl; }
};
 
class Derived : public Base
{
  public:
    virtual ~D() { cout <<"D::D~()"<<endl; }
};
 
Base* pBase = new Derived[10];
delete[] pBase;

C语言补课

我先不说这段C++的程序在什么情况下能正确调用派生类的析构函数,我还是先来说说C语言,这样我在后面说这段代码时你就明白了。

对于上面的:

1
Base* pBase = new Derived[10];

这个语言和下面的有什么不同吗?

1
2
3
Derived d[10];
 
Base* pBase = d;

一个是堆内存动态分配,一个是栈内存静态分配。只是内存的位置和类型不一样,在语法和使用上没有什么不一样的。(如果你把Base 和 Derived想成struct,把new想成malloc() ,你还觉得这和C++有什么关系吗?)

那么,你觉得pBase这个指针是指向对象的,是对象的引用,还是指向一个数组的,是数组的引用?

于是乎,你可以想像一下下面的场景:

1
2
3
4
5
int *pInt; char* pChar;
 
pInt = (int*)malloc(10*sizeof(int));
 
pChar = (char*)pInt;

对上面的pInt和pChar指针来说,pInt[3]和pChar[3]所指向的内容是否一样呢?当然不一样,因为int是4个字节,char是1个字节,步长不一样,所以当然不一样。

那么再回到那个把Derived[]数组的指针转成Base类型的指针pBase,那么pBase[3]是否会指向正确的Derrived[3]呢?

我们来看个纯C语言的例程,下面有两个结构体,就像继承一样,我还别有用心地加了一个void *vptr,好像虚函数表一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct A {
    void *vptr;
    int i;
};
 
struct B{
    void *vptr;
    int i;
    char c;
    int j;
}b[2] ={
    {(void*)0x01, 100, 'a', -1},
    {(void*)0x02, 200, 'A', -2}
};

注意:我用的是G++编译的,在64bits平台上编译的,其中的sizeof(void*)的值是8。

我们看一下栈上内存分配:

1
struct A *pa1 = (struct A*)(b);

用gdb我们可以看到下面的情况:(pa1[1]的成员的值完全乱掉了)

1
2
3
4
5
6
(gdb) p b
$7 = {{vptr = 0x1, i = 100, c = 97 'a', j = -1}, {vptr = 0x2, i = 200, c = 65 'A', j = -2}}
(gdb) p pa1[0]
$8 = {vptr = 0x1, i = 100}
(gdb) p pa1[1]
$9 = {vptr = 0x7fffffffffff, i = 2}

我们再来看一下堆上的情况:(我们动态了struct B [2],然后转成struct A *,然后对其成员操作)

1
2
3
4
5
6
7
8
struct A *pa = (struct A*)malloc(2*sizeof(struct B));
struct B *pb = (struct B*)pa;
 
pa[0].vptr = (void*) 0x01;
pa[1].vptr = (void*) 0x02;
 
posted on 2014-05-06 23:41 冬瓜 阅读(324) 评论(0)  编辑 收藏 引用 所属分类: 转贴C++

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理