“C++的数组不支持多态”?

“C++的数组不支持多态”?

先是在微博上看到了个微博和云风的评论,然后我回了“楼主对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)》最后时,才知道了说的是第二种情况。也就是下面的这个示例(我加了虚的析构函数这样方便编译):

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语言,这样我在后面说这段代码时你就明白了。

对于上面的:

Base* pBase = new Derived[10];

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

Derived d[10];

Base* pBase = d;

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

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

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

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,好像虚函数表一样:

    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。

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

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

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

(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 *,然后对其成员操作)

    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;

    pa[0].i = 100;
    pa[1].i = 200;

用gdb来查看一下变量,我们可以看到下面的情况:(pa没问题,但是pb[1]的内存乱掉了)

(gdb) p pa[0]
$1 = {vptr = 0x1, i = 100}
(gdb) p pa[1]
$2 = {vptr = 0x2, i = 200}
(gdb) p pb[0]
$3 = {vptr = 0x1, i = 100, c = 0 '\000', j = 2}
(gdb) p pb[1]
$4 = {vptr = 0xc8, i = 0, c = 0 '\000', j = 0}

可见,这完全就是C语言里乱转型造成了内存的混乱,这和C++一点关系都没有。而且,C++的任何一本书都说过,父类对象和子类对象的转型会带来严重的内存问题。

但是,如果在64bits平台下,如果把我们的structB改一下,改成如下(把struct B中的int j给注释掉):

    struct A {
        void *vptr;
        int i;
    };

    struct B{
        void *vptr;
        int i;
        char c;
        //int j; <---注释掉int j
    }b[2] ={
        {(void*)0x01, 100, 'a'},
        {(void*)0x02, 200, 'A'}
    };

你就会发现,上面的内存混乱的问题都没有了,因为struct A和struct B的size是一样的:

(gdb) p sizeof(struct A)
$6 = 16
(gdb) p sizeof(struct B)
$7 = 16

注:如果不注释int j,那么sizeof(struct B)的值是24。

这就是C语言中的内存对齐,内存对齐的原因就是为了更快的存取内存(详见《深入理解C语言》)

如果内存对齐了,而且struct A中的成员的顺序在struct B中是一样的而且在最前面话,那么就没有问题。

再来看C++的程序

如果你看过我5年前写的《C++虚函数表解析》以及《C++内存对象布局 上篇下篇》,你就知道C++的标准会把虚函数表的指针放在类实例的最前面,你也就知道为什么我别有用心地在struct A和struct B前加了一个 void *vptr。C++之所以要加在最前面就是为了转型后,不会找不到虚表了。

好了,到这里,我们再来看C++,看下面的代码:

#include
using namespace std;

class B
{
  int b;
  public:
    virtual ~B(){ cout <<"B::~B()"<<endl; }
};

class D: public B
{
  int i;
  public:
    virtual ~D() { cout <<"D::~D()"<<endl; }
};

int main(void)
{
    cout << "sizeB:" << sizeof(B) << " sizeD:"<< sizeof(D) <<endl;
    B *pb = new D[2];

    delete [] pb;

    return 0;
}

上面的代码可以正确执行,包括调用子类的虚函数!因为内存对齐了。在我的64bits的CentOS上——sizeof(B):16 ,sizeof(D):16

但是,如果你在class D中再加一个int成员的问题,这个程序就Segmentation fault了。因为—— sizeof(B):16 ,sizeof(D):24。pb[1]的虚表找到了一个错误的内存上,内存乱掉了。

再注:我在Visual Studio 2010上做了一下测试,对于 struct 来说,其表现和gcc的是一样的,但对于class的代码来说,其可以“正确调用到虚函数”无论父类和子类有没有一样的size。

然而,在C++的标准中,下面这样的用法是undefined! 你可以看看StackOverflow上的相关问题讨论:《Why is it undefined behavior to delete[] an array of derived objects via a base pointer?》(同样,你也可以看看《More Effective C++》中的条款三)

Base* pBase = new Derived[10];

delete[] pBase;

所以,微软C++编程译器define这个事让我非常不解,对微软的C++编译器再度失望,看似默默地把其编译对了很漂亮,实则误导了好多人把这种undefined的东西当成defined来用,还赞扬做得好,真是令人无语。就像微博上的这个贴一样,说VC多么牛,还说这是OO的特性。我勒个去!

现在,你终于知道Base* pBase = new Derived[10];这个问题是C语言的转型的问题,你也应该知道用于数组的指针是怎么回事了吧?这是一个很奇葩的代码!请你不要像那些人一样在微博上和这里的评论里高呼并和我理论到:“微软的C++编译器支持这个事!”。

最后,我越来越发现,很多说C++难用的人,其实是不懂C语言

(全文完)

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

好烂啊有点差凑合看看还不错很精彩 (52 人打了分,平均分: 4.25 )
Loading...

“C++的数组不支持多态”?》的相关评论

  1. @GeniusVczh 的原文 《如何设计一门语言(一)——什么是坑(a)》中挖的坑本来就是黑C的,数组与指正可以任意转换并浪费了T[]这种类型,C++只是继承了这些坑而已(只是C++类型指针具有更丰富的多态功能),并且很多书籍中也都介绍了要绕过此坑。微软看似好心跟这个坑装了个盖子实则用心险恶,潜台词就是”你用了微软的东西就只能在微软DNA下生存,否则!!嘿嘿,小心断腿!”。

  2. 当初学的第一语言是C,刚接触细佳佳的时候,实在不能接受C++面向对象的概念,总是喜欢用malloc。。。。后来到了学c#和java才明白原来数组是对象。。。而不是一块连续的内存区域。。。。我觉得C++最棒的功能就是保留了指针的概念,保留了内存管理的功能,或许直接学面向对象编程语言的程序员不能接受指针。。。

  3. 对undefined的特性进行支持,也不是不可以呀?没有说必须core掉,但是了解这个区别是非常必要的,也应该避免使用。

  4. undefine behavior的意思是,c/c++标准不去干涉编译器的实现;并且也认为,没有哪个2货会自找不自在(去碰这种东西),所以编译器厂商也不必侦测、处理相关问题。

    如果真有自找不自在的家伙,那么,他的程序最终是正确运行、格式化C盘、覆盖CMOS数据甚至烧掉显卡,都咎由自取。编译器不必负任何责任。

    最常见的undefine behavior,就是谭浩强最喜欢折腾的“i=i++”和“i++ + ++i”。

  5. 有种说法是C是一种“中级”语言,有时候需要关心字节对齐,数据在内存中的形态,字节序等等,就是这类语言的特征之一。C++的悲催就在于要完全兼容这些特性。Java里面就完全看不到这些东西了。所以,同意楼主的说法,C++没有用好,是因为C没有过关。

  6. @gouchaoer
    如果父类和子类都定义了虚方法,那么在实例地址的开始位置会存放虚方法表的一个指针,自动调用析构函数就不成问题,如果父类或是子类没有定义虚析构方法,那么对应的类就不能自动析构

  7. 1, “很多说C++难用的人,其实是不懂C语言。” 不能支持这个观点更多

    2, 关于标准的undefined behavior, vc2010的实现无可厚非。 vc的实现和博主期望不一致而已。 既然是 undefined,那他自然可以和博主的实现不一致,反过来说g++对这个未定义的实现和博主的期望一致。其实博主后来的实验例子,也不过是在试图弄清g++的实现方式而已(注意,是g++的实现,不是iso c++的规范)。
    至于误导问题,c学得不好的人,编译器是应该实现出一个符合多数人直觉的行为,还是给用户一个难查的bug(也可能不表现出bug症状,编译也不会警告或报错——比如博主后面举的不出错的例子),这是个产品设计哲学的问题,不能据此就说ms做的东西坑人。 不懂c是不懂c的人的问题, 不是vc的问题。

    3, stroustrup 本人也曾表示过, 比可能要求所有的c++编译器实现都和标准完全兼容,更不用说各个编译器的实现彼此之间完全一致了。 vc对这个undefined behavior的实现方式, 可以认为是vc的一个方言, g++也有很多自己的方言,并且这些方言性质的特性, 很多后来都进入了c++标准,这也符合c++标准化过程中的一个原则 standardizing existing standard。 反过来,一些特性由于遭到几乎所有编译器的抵制,也会被标准移除,比如模板关键字export

    1. “很多说C++难用的人,其实是不懂C语言。”
      c语言和unix的发明者 汤普森 说c++是一堆自相矛盾的垃圾

  8. @Joye
    软件开发难度不大,涌入更多不合格的开发者,然后骂人的就是客户了,把该懂的东西都弄懂,无论用什么语言。
    内存对齐这个基础知识点在我两年前还在校园学C语言的时候,印象中很多书籍和资料都说到,甚至连一些asm的简读都不忘提及一下,就是这么一个基础,还绊倒了一群声称“学好C和JAVA”的JAVA程序员,看来JAVA程序员的整体基础素质退化已是不争的事实。

  9. can’t agree more.
    vs对于struct和class采用了不同的处理方法,个人还是比较支持将C和C++适当的分离开来。非常多的人先学C然后学C++,很多问题习惯“自然而然”的沿用了下来。
    不赞同原文对于undefined的理解,这种争论感觉就像“吃鸡蛋先从大头打还是先从小头打开”一样。除非哪天C++标准明确改变了这种undefined态度,本人还是支持编译器自己默认设置,不过这种模棱两可的设定总是隐藏着危险。
    @shuiren

  10. 额,楼主文章开头代码有点笔误:

    “`
    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;
    “`

  11. 最开始的一个代码片段写的有点笔误,构造函数和析构函数的名称跟类名不一致。博主请更正。

  12. struct A {
    void *vptr;
    int i;
    };

    struct B{
    void *vptr;
    int i;
    char c;
    //int j; <—注释掉int j
    }b[2] ={
    {(void*)0x01, 100, 'a'},
    {(void*)0x02, 200, 'A'}
    };

    这段代码 sizeof(A) 和 sizeof(B) 在64 平台上都是16吗? 没实证,求解?

  13. 记得以前看过Gosling发明java的轶事,说是因为他觉得c++太难了。可见对一种语言表达不满最好的方法就是开发一种更好的。

  14. 仔细看看加代码验证,发现其实这不仅仅是调用析构函数问题,调用其他虚函数也一样会出现问题。使用基类指针数组时,指针增长步长是sizeof(B)的,所以当sizeof(B) < sizeof(D)时,对象地址就错误了。

  15. 我觉得在这个问题上,数组中允许多态的确不太好。应该只允许放对象指针才比较合理。

  16. @suninrain

    估计楼主那洋洋洒洒的几千字,你没怎么看,友情劝一下,回过头再看一下楼主强调的

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

  17. 顶一下楼主! 个人一直对C++不抱好感,但楼主能把这些“坑”详细的说明一下,也是一钟情怀·····表示继续关住楼主······

  18. @一米阳光
    难道undefined的意思就是博主用的compiler的实现方式就是对的,msvc的实现方式就是错的?
    呵呵,咱能有点自己的见解,别见谁说个啥都去迷信好么

  19. Base *pBase = new Derived[n] ;
    这句由数组向指针转换操作是没有问题的. 从逻辑和语义都说得通.

    真正有问题的是这句
    pBase[1];
    这里隐含了一个 由指针向数组转换的操作. 而且假设了数组的元素大小与指针类型相同.

    这个假设不是在所有情况下都成立的. 支持纯为了程序员方便才做的假设(如果报waring会很麻烦).
    如果你的代码不满足这个假设,只能说明你不会用cpp

  20. 为什么把类的virtual都去掉,不会报错呢?只调用父类的析构函数,为什么???

  21. 楼主,你的那篇csdn的博客:《C++虚函数表解析》怎么一直不修正一下这个错误?

    (int*)(&b)明显不是虚函数表的地址,在我看来,这不过就是对象b的地址(或者叫b的入口地址或首地址),int*只是做了一个类型转换而已(32位平台下,int的长度跟指针的长度一样,都是4字节),虚函数表的地址应该是对象b中第一个单元所存储的值,因此,*(int*)(&b)才是第一个虚函数表的地址,而(int*)*(int*)(&b)就是再做一个类型转换,使之能操控虚函数表中的元素,即函数指针

  22. FBW :
    楼主,你的那篇csdn的博客:《C++虚函数表解析》怎么一直不修正一下这个错误?
    (int*)(&b)明显不是虚函数表的地址,在我看来,这不过就是对象b的地址(或者叫b的入口地址或首地址),int*只是做了一个类型转换而已(32位平台下,int的长度跟指针的长度一样,都是4字节),虚函数表的地址应该是对象b中第一个单元所存储的值,因此,*(int*)(&b)才是第一个虚函数表的地址,而(int*)*(int*)(&b)就是再做一个类型转换,使之能操控虚函数表中的元素,即函数指针

    那个*换行时隔开了,特意再强调一下,*(int*)(&b)才是第一个虚函数表的地址

  23. Base *pBase = new Derived[n]; 把一个派生类的数组赋值给了基类指针,这注定没有正确的行为,编译器应该直接报ERROR

  24. C++成功在于兼容C,失败也在于兼容C。

    C++兼容C,开始流行,中间兴盛,后面臃肿。现在就是了,臃肿到一种让人恶心的地步,但是除了臃肿之外,却还有模板这种高级的东西,C++把C、OOP、模板这些东西已一种极其扭曲的方式结合在一起,可以说是一个奇迹了。

    如果C++当初完全不考虑兼容C,而是以新的概念设计,那么肯定不是像现在这么流行,甚至没多少人学,当然是前期,到了中期,肯定会有越来越多的人开始重视,到了现在,估计会和Java一个地位,甚至远高于Java。

    可惜的是,C++选择了一个快速崛起的方式,就和我国的政策一样,先污染后治理,看似有用,但是他们却不知道,一旦污染了,就很难治理了。
    C++选择了兼容C,现在就很难和C分离了,每弄一个新的概念,加一个新的东西,不得不以一种极端变态的方式与C结合在一起。

  25. 就这个问题, 恐怕只在C++标准的范畴内讨论不是太够吧, 不提ABI(http://en.wikipedia.org/wiki/Application_binary_interface)的话, 恐怕很难说清楚.

    就比较流行的GC++ABI(Generic C++ ABI)和aeabi说一下我的认识.

    C/C++中的array new, 都可能会涉及到array cookies, 下面是GC++ABI和aeabi中关于array cookies的描述
    GC++ABI: http://mentorembedded.github.io/cxx-abi/abi.html#array-cookies
    aeabi: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0041d/IHI0041D_cppabi.pdf (3.2.2.1 array cookies)

    在GC++ABI中, array cookies通常是一个size_t, 记录着整个数组中elem的个数, 而没有记录elem的size, 因此在array delete的时候, 获取elemsize的唯一途径恐怕就是根据当时给定的类型来sizeof(T)了
    p = operator new[](n * sizeof(T) + padding, ARGS)
    p1 = (T*) ( (char *)p + padding )

    if padding > 0
    *( (size_t *)p1 – 1) = n

    for i = [0, n)
    create a T, using the default constructor, at p1[i]

    而在aeabi中, array cookies记录了两个size_t, elemsize和elemcount, 这就给了array delete提供了更多的实现空间, 从而可以不依赖给定类型的sizeof(T), 而是cookies中记录的size对array中的elem逐一析构
    struct array_cookie {
    std::size_t element_size; // element_size != 0
    std::size_t element_count;
    };
    // element_size_of() takes a pointer to the user array and returns a reference to the
    // element_size field of the cookie.
    inline size_t& element_size_of(void* user_array)
    {
    return cookie_of(user_array).element_size;
    }
    extern “C” void* __aeabi_vec_dtor_cookie(void* user_array, void* (*destructor)(void*))
    { // The meaning of this function is given by the following model implementation…
    // like:
    // __cxa_vec_dtor(user_array, element_count_of(user_array),
    // element_size_of(user_array), destructor);
    return user_array == NULL ? NULL :
    __aeabi_vec_dtor(user_array, destructor,
    element_size_of(user_array), element_count_of(user_array));
    }

    GCC4.4.3中的实现
    __aeabi_vec_dtor_cookie, 可以看到cookies的确是两个size_t
    extern “C” void *
    __aeabi_vec_dtor_cookie (void *array_address,
    abi::__cxa_cdtor_type destructor)
    {
    if (!array_address)
    return NULL;

    abi::__cxa_vec_dtor (array_address,
    reinterpret_cast(array_address)[-1],
    reinterpret_cast(array_address)[-2],
    destructor);
    return reinterpret_cast (array_address) – 2;
    }

    cookies中的两个size_t, 是在__cxa_vec_new2中记录的(_GLIBCXX_ELTSIZE_IN_COOKIE must be defined), 不过在 __cxa_vec_delete2中就没有用到, 应该是为了遵循GC++ABI的缘故
    extern “C” void *
    __cxa_vec_new2(std::size_t element_count,
    std::size_t element_size,
    std::size_t padding_size,
    __cxa_cdtor_type constructor,
    __cxa_cdtor_type destructor,
    void *(*alloc) (std::size_t),
    void (*dealloc) (void *))
    {

    if (padding_size)
    {
    base += padding_size;
    reinterpret_cast (base)[-1] = element_count;
    #ifdef _GLIBCXX_ELTSIZE_IN_COOKIE
    reinterpret_cast (base)[-2] = element_size;
    #endif
    }

    }

    所以说, array delete这事儿, 很大程度上要看ABI了. 不过还是慎用奇葩的写法最好 :)

  26. @clonne
    赞同
    C++是一门比较畸形的语言,很大原因是他要兼容C(当然也包括当年很多概念还不成熟)。
    不能因为要兼容C导致C++有问题,就说C语言设计的烂,先来后到的问题。
    即使懂了C语言,C++还是难用,坑太多。

  27. 我认为这是C++对象成员函数调用问题,如果纠结这个问题,推荐看下《深度探索C++对象模型》,里面说到过,具体章节忘记了。C++的一个实体是具备类型信息的,在调用其成员函数时可以直接决义到调用哪个函数,链接器完全可以实现同时虚函数还可以减少虚表的开销。
    class A
    {
    public:
    Print()
    {
    cout<<"this is a!"<<endl;
    }
    };

    class B
    {
    public:
    Print()
    {
    cout<<"this is b!"<Print();

    return 0;
    }
    这会执行B的Print。而a只是在调用的时候传递了一个this指针而已,而这里会影响成员函数中类成员调用正确,因为你的指针是错误的。这里的Print不是虚函数,因此可以根据b指针的类型来直接决定函数。
    引用和指针不具备所指向数据的真实类型,因为可能是子类,当把Print函数改成虚函数就完全变了,调用的时候是通过虚表去寻找的。

    但真实情况是各编译器实现却是不同的,并没有统一的标准。
    class A
    {
    public:
    virtual void Print()
    {
    cout<<"this is a!"<<endl;
    }

    virtual ~A()
    {

    }
    };

    int main()
    {
    A pA[10];
    memset(pA, 0, sizeof(A)*10);

    return 0;
    }
    在VC6.0是可以正常运行,在SUSE10下面却core了,但是如果你把pA用new出来,delete的时候却又core了。
    A对象无论存储在哪,其类型都却定是A,不可能是其子类或基类,本应链接的时候就能确定其析构函数的入口,但各个编译器实现不同就出现不同结果。

  28. 文章太长没看完,看了前面猜测就是基础的类型退化问题,要是真是博主说的只释放了数组没释放数组内容那也没什么好说了(笑)。
    现在院校培养出来的可以说都是代码机器,用着"高级"语言写着垃圾代码,不注意代码可读性,滥用内存,对问题不求甚解。尤其这几年flash游戏崛起,让一批早年做flash动画的会点as就敢自称程序员,让这种问题更甚。
    博主和风云都的文章见解独到我个人很喜欢,两位何必为这些人动肝火写文章斗嘴,不值不值。

  29. 博主,我想说下您的这段代码
    using namespace std;

    class B
    {
    int b;
    public:
    virtual ~B(){ cout <<"B::~B()"<<endl; }
    };

    class D: public B
    {
    int i;
    public:
    virtual ~D() { cout <<"D::~D()"<<endl; }
    };

    int main(void)
    {
    cout << "sizeB:" << sizeof(B) << " sizeD:"<< sizeof(D) <<endl;
    B *pb = new D[2];

    delete [] pb;

    return 0;
    }
    在继承D类(派生类)继承了B类(基类)的过程之后,除了加载初始化B类的时候,有为B类的成员变量b分配内存外,在加载初始化D类时,应该有再为B类的成员变量b分配内存,所以sizeof(D)跟sizeof(B)总会差4个字节(32位机器中)

回复 dospeng 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注