[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

本文参考文献:GeekBand课堂内容,授课老师:侯捷

:深度探索C++对象模型(侯捷译)

:网络资料: http://www.leavesite.com/geekband-cpp-5.html

http://blog.csdn.net/wudaijun/article/details/9273339

本周的课题是:“ 为上周题目中的 Fruit和Apple 添加 构造函数与 析构函数, 并在构造函数与析构函数中打印控制台信息,观察构造和析枸调用过程。然后为Apple类重载::operator new和 ::operator delete,在控制台打印信息,并观察调用结果。”

虽然其中构造与析构调用过程上次代码里已经实现了,并且比现在这份还要完善一些。但做为开场白再讲一遍比较好!

首先,先看下 类的结构。Apple 类继承自基类Fruit

1 //基类
 2 class Fruit
 3 {
 4 public:
 5     //使用自带的构造函数
 6     Fruit()
 7     {
 8         cout << "Call Fruit Constructor.this =  " <<this<< endl;
 9     }
10     //打印变量内存地址
11     void print(){}
12     //虚函数的影响
13     virtual void process(){}
14 
15     virtual ~Fruit()
16     {
17         cout << "Call Fruit Destructor = " << this << endl; 
18     }
19 
20 private:
21     int no;
22     double weight;
23     char key;
24 };
25 
26 //这里考虑自己本身的虚函数,及基类的虚函数
27 class Apple : public Fruit
28 {
29 public:
30     //使用默认的构造函数
31     Apple()
32     {
33         cout << "Call Apple Constructor.this =  " << this << endl;
34     };
35     //打印成员数据
36     void save(){}
37     virtual void process(){}
38     virtual ~Apple()
39     {
40         cout << "Call Apple Destructor.this = " << this << endl;
41     }
42 
43     //测试二、抛出异常
44     static void* operator new(size_t size);
45     //测试三、没有抛出异常,此版本需要注释掉测试二
46     //static void* operator new(size_t size, const std::nothrow_t& nothrow_value);
47     
48     //测试四、带有调试信息的版本,此版本需要注释掉测试二、测试三
49     //inline void* Apple::operator new(size_t size, const char* file, int line);
50     
51     //delete 版本
52     static void  operator delete(void* ptr, size_t size) throw();
53 
54     //测试五、测试数组
55     static void *operator new[](size_t size);
56     static void operator delete[](void *ptr);
57 
58 private:
59     int size;
60     char type;
61 };

那么问题来了,Apple 类和Fruit谁先构造、又谁先析构呢?进而思考,基类和子类谁更大一些?

众所周知,子类拥有父类的一切信息,而且子类有些信息更具体,比如鸟都有翅膀,这是共性。但是比如啄木鸟的嘴特别长,这就是特性。自然界是共性与特性的统一。

不过从哲学的角度来看,如“人是社会关系的总和”,讲的也是这个道理。

扯得有点远了,看图!所以构造时先构造内部,然后构造外部,析构时正好相反!

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

可以充分证明这个观点,还有问题的话,拷贝我上篇blog代码,可以有更详细的分析,这里就不展开讲了。毕竟只是开场白!

一、new和delete重载的实现及分析

new:指我们在C++里通常用到的运算符,比如A a = new A; 对于new来说,有new和::new之分,前者位于stdoperator new():指对new的重载形式,它是一个函数,并不是运算符。对于operator new来说,分为全局重载和类重载,全局重载是void ::operator new(size_t size),在类中重载形式 void A::operator new(size_t size)。还要注意的是这里的operator new()完成的操作一般只是分配内存,事实上系统默认的全局::operator new(size_t size)也只是调用malloc分配内存,并且返回一个void指针。而构造函数的调用(如果需要)是在new运算符中完成的。
-----------wudaijun blog

1、重载时,一个类为空怎么处理?

一个类中,如果什么数据都没有!打印结果却是1

class Empty
{
};

int main(int argc, char** argv)
 {
    std::cout << sizeof(Empty) << std::endl;
    return 0;
}

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

所以我们为类进行new 重载时应该也要考虑到这一点。至于为什么是1,不是0,也而不是其他的数据。我没弄清楚。但根据调试结果来分析,

我们在重载应该考虑到这一点。

首先应该判断下size是否为0。有指针时也要判断指针是否为空。

inline void* Apple::operator new(size_t size)
{
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else 
    {
        throw bad_alloc();
    }
}

2、operator new() 和 operator delete() 会自动转换为static 成员

由叶卡同学的blog中记录的 C++ Primer 557所示,成员operator new() 和 operator delete()会自动成为static成员。

因此,它们没有this指针,而且也不会修改物件内容,仅仅作为开辟空间、和清楚空间的作用!

3、operator new() 的三种形式:

throwing (1)    
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2)    
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)    
void* operator new (std::size_t size, void* ptr) throw();

第1、2种的区别 是有无抛出异常,其中有抛出异常的还可以进一步抛出信息,下面将会分析。

第3种 placement new,它也是对operator new的一个重载,定义于中,它多接收一个ptr参数,但它只是简单地返回ptr。这里暂时没有详细分析,请同学自行查阅资料。(我上面的推荐资料里就有)

/*
测试二、抛出异常的版本
*/

inline void* Apple::operator new(size_t size)
{
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else 
    {
        throw bad_alloc();
    }
}

运行图如下:

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

从上图分析得出,Fruit的Size为32,Apple 的Size为40。与上述相对应。

/*
测试三、没有抛出异常的版本
*/


inline void* Apple:: operator new(size_t size, const std::nothrow_t& nothrow_value)
{
    //即使是空类,大小也为1
    if (size == 0)
    {
        return malloc(1);
    }
    else
        std::cout << "call Apple::operator new nothrow" << std::endl;
        return malloc(size);
}

这个版本是没有返回异常信息的版本

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

如图所示,New的过程中那些打印信息并没有显示。

new 这类信息往往会用在调试代码阶段。能比较方便的显示出行数及文件信息。

*
    测试四、抛出异常,并带有调试信息的版本
    此版本使用时,会对以上两个版本发生冲突,需要注释掉另外两个函数,及使用
*/


inline void* Apple::operator new(size_t size, const char* file, int line)
{
    //即使是空类,大小也为1
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        std::cout << "call A::operator new on file:" << file << "  line:" << line << std::endl;
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else {
        throw bad_alloc();
    }
}

在测试头部也要添加信息

//测试四、打开注释
//#define new new(__FILE__, __LINE__)

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

如图所示,显示了文件、及行数信息,方便调试。

4、何时重载类中、全局的 operator new()

/*
        测试一、栈空间,使用自带的new 和全局new
    */

    Apple ptrApple;


    Fruit *ptr = new Fruit();
    delete ptr;
    Apple* ptr1 = new Apple();//Apple 是临时变量,所占空间是以new动态分配而得,并由p指向,占用空间为堆
    delete ptr1;

这里有两种方法使用Apple 类,第一种为栈调用的方法,第二种为堆调用的方法(自己malloc)。这两种方法调用new 和delete的位置不同。

如图所示, 这里实际上有几个步骤:

1、分配内存.

2、指针类型转换

3、调用构造函数

分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

通过以上结果对比,作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。

既先查找类中的operator new()和 operator delete(),然后再执行全局operator new()和 operator delete()。

5、多维数组的重载

/*
    测试五、类中重载new[] 和 delete[]
*/
inline void* Apple::operator new[](size_t size)
{
    //即使是空类,大小也为1
    if (size == 0)
    {
        return malloc(1);
    }
    cout << "This is Apple New[]! Now allocating space :" << size << "Byte!" << endl;
    return malloc(size);
}

inline void Apple::operator delete[](void *ptr)
{
    if (ptr)
    {
        cout << "This is Apple Delete[], Now free space!" << endl;
        free(ptr);
    }
    else
    {
        ptr = NULL;
    }
}
Apple *ptr3 = new Apple[3];
    cout << "ptr3[0] addr: " << ptr3 << endl;
    cout << "ptr3[1] addr: " << ptr3 + 1 << endl;
    cout << "ptr3[2] addr: " << ptr3 + 2 << endl;
    delete[] ptr3;
    ptr3 = NULL;

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

下面用图来解释下,(此图源于某blog内容,后面图保存了,却找不到来源,请作者勿怪,如有侵权,请联系我,谢谢)

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

delete的过程

[GeekBand] C++ 内存分布—— new和delete重载的实现及分析

烦请路过的朋友,批评指针。感谢网络的无私奉献者。 修改于 2016.08.15 17:28

内容修改中,8月15日晚11:30分前

上传最新版本
原文链接: https://www.cnblogs.com/xuhe/p/5771508.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/238751

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午5:56
下一篇 2023年2月13日 下午5:57

相关推荐