C++内存管理(new operator/operator new/operator delete/placement new)

new operator

我们平时使用的new是new操作符(new operator),就像sizeof一样是语言内置的,不能改变它的含义,功能也是一样的

比如:

string *ps = new string("Memory Management");

相当于

void *memory = operator new(sizeof(string)); // 得到未经处理的内存,为String对象
call string::string("Memory Management") on *memory; // 调构造函数将字符串放到内存
string *ps = static_cast<string*>(memory); // 使ps指针指向新的对象

new操作符总是做两件事:

1.调用相应的operator new分配内存

2.调用相应的构造函数

如下代码:

class T{
    public:
    T(){
        cout << "构造函数。" << endl;
    }  

    ~T(){
        cout << "析构函数。" << endl;
    }  

    void * operator new(size_t sz){  

        T * t = (T*)malloc(sizeof(T));  //operator new就是简单的分配内存即可
        cout << "内存分配。" << endl;
        return t;
    }  

    void operator delete(void *p){
        free(p);
        cout << "内存释放。" << endl;
        return;
    }
};  

int main()
{
    T * t = new T(); // 先内存分配 ,再构造函数
    delete t; // 先析构函数, 再内存释放
    return 0;
} 

operator new

重载new操作符,你可以改变怎样为对象分配内存。new操作符调用一个函数来完成必需的内存分配,你可以重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。

重载的new操作符

void * operator new(size_t sz)
{
    void* pm=malloc(sz);
    return pm;
}

返回类型是个void*类型的指针,未初始化的内存,你可以写一种operator new函数,在返回一个指针之前可以初始化内存以存储一些数值,可是一般不这么做。你还能添加额外的參数重载函数operator new,可是第一个參数类型必须是size_t。new可自己计算类型的大小对应类型的指针。

一般不会直接调用operator new,但是有时候还会用到,比如在STL中:

Test *pt2=(Test*)::operator new(sizeof(Test));
::operator delete(pt2);

返回一个指针,指向足够容纳一个Test类型的对象的内存。operator new仅仅分配内存。和构造函数无关。把operator new 返回的未经处理的指针传递给一个对象是new操作符的工作。

比如:

string *ps = new string("Memory Management");

相当于

void *memory = operator new(sizeof(string)); // 得到未经处理的内存,为String对象
call string::string("Memory Management") on *memory; // 调构造函数将字符串放到内存
string *ps = static_cast<string*>(memory); // 使ps指针指向新的对象

operate delete

delete 是先调用析构函数,再调用operator delete,释放空间,可以从上面运行结果可以看出。

可以自己重载delete

void operator delete(void *p)
{
    free(p);
}

operator new

重载new[]操作符

void* operator new[](size_t sz)
{
    void *pm=malloc(sz);
    return pm;
}

operator new 和operator new功能都是仅仅分配内存,operator new是给new用的,operator?new[]是给new[]用的,new[]实际上比new多分配了四字节用于存储对象的数量

而这多的四个字节已经作为参数加到调用void*?operator?new;的size里了,也就是说当调用?A*?ap?=?new?A5;时,内部还调用了?operator?new;作用就是申请了5*size(A)?+?4字节的内存。new[]会对数组的每个调用对象的构造函数初始化。同样new[]可自己计算类型的大小对应类型的指针。

多出来的4个字节,相当于cookie,用来记录申请了多大,到时候调用delete[]释放时就可以知道释放多大空间。

operator delete

重载delete[]操作符

void operator delete[](void *p)
{
    free(p);
}

在operator new[]需要给定大小,但是delete[]不需要给定大小,就算给了大小,也没有意义,是无效的,释放时候是读取上面说到的cookie中的信息来释放的。

placement new

new分为两步走,先分配内存,后调用构造函数。那么,可不可以保持一块内存,反复构造析构?这样可以省略中间的多次分配内存。由于申请内存会有大量的系统开销。C++标准也是这么想的,所以他们提供了placement new,定位new。

int *p=(int*)malloc(sizeof(int)*10);
new(p)int(10);

表示将10放在p的空间的第一位。

void * operator new(size_t, void *location)
{  
    return location;
}

它也是new操作符的一个使用方法,须要使用一个额外的变量(buffer)。当new操作符隐含调用operator new函数时。把这个变量传递给它。被调用的operator new函数除了带有强制的參数size_t外,还必须接受void*指针參数。指向构造对象占用的内存空间。

如果要将一个元素放在其他下标位置中,可以重载new

void* operator new(size_t sz,void *ptr,int pos)    //第1个参数必须但不需要传递,由new自动计算的,第2个参数传递void*指针,第3个参数传递位置
{
    return &ptr[pos];
}

new(p,3)int(10);    //将10放在下标为3的空间中

关于new的内部实现机制

我们都知道new分为两步完成,但是new是怎样完成这两步的呢??

开始我以为是在new内部封装了一个两个函数,一个用来分配内存,另一个用来调用相应的构造函数。

但事实上并非如此,根本就没有一个“隐藏”的new函数,编译器会自动将new改写成一个内存分配函数,一个构造函数。?

这里其实还有一个更本质的问题需要说明:new的本质是关键字,编译器操作关键字的内幕可能与我们的想象大不相同。能够重载的是函数,是运算符,关键字是不能重载的,这也是为什么运算符重载前面必须要有operator的原因。+(int a),new(int a)都是错的。?

简而言之,new在编译器的操作下会变成两部分代码,一部分分配内存,一部分调用构造函数。

这其实是可以做实验验证的:?

就在上述代码中加入new T并打上断点,再F10一步一步地调试,发现先进入类中的operator new,执行完后并没有到new T的下一行,而是依然停留在new这一行,继续F10进入构造函数。就是说new T这一行代码停留了两次。这意味着编译器确实将new T这一行代码翻译成了两次函数调用。

第二种验证方式就是直接使用反汇编查看代码(推荐):?

  可以看到的确有两个call,第一个call T::operator new( )。第二个call T::T( )。注意:真正的汇编中没有具体的函数名,只有相应的地址。显示函数名是VS的一个比较便利的功能。

  可以看到的确有两个call,第一个call T::operator new( )。第二个call T::T( )。注意:真正的汇编中没有具体的函数名,只有相应的地址。显示函数名是VS的一个比较便利的功能。

::new与new

  在全局命名空间中有一个自带的、隐藏的operator new专门用来分配内存。默认情况下编译器会将new这个关键字翻译成这个operator new和相应的构造函数。

  但在有的情况下,用户自己会在类中重载operator new,这种情况下,编译器默认会使用类中重载的operator new(本质上因为编译器会从命名空间由内而外查找自己想要的函数,选用第一个)。

  如果我们想要继续使用默认的operator new,就应该写成::new 字面意思就是调用最外层命名空间中的operator new

 值得一提的是最外层的(也是默认的)operator new也是可以被重载的。通过这种方式我们可以改变所有new的部分行为。

总结

C++中是通过new和delete操作符进行动态内存管理的。

用一张图说明new和delete的含义:

new和delete以及malloc和free一样,要成对使用。

这是string *s = new string("a value");这句表达式内部的实现:

可以得出:

(初始化一个对象时)new内部的调用顺序:new —— operator new —— malloc —— 构造函数 (先申请空间,再调用构造函数)

(初始化若干个对象时)new内部的调用顺序:new —— operator new [] —— malloc —— 构造函数(先申请数组所有成员的空间大小+4,调用数组中每个对象构造函数)

(delete单个对象时)delete对象时,调用顺序为:delete —— 析构函数 —— operator delete —— free (先调用析构函数,再释放空间)

(delete多个对象时)delete对象时,调用顺序为:delete [] —— 析构函数 —— operator delete[] —— free(先调用数组中每个对象的析构函数,再释放整个数组的空间,空间信息就在多申请的4个字节中)

new/delete与malloc/free二者的差别

1.它们都是动态管理内存的入口。

2.malloc/free是C/C++标准库的函数,new/delete是C++操作符。

3.malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造析构函数进行初始化与清理(清理成员)。

4.malloc/free需要手动计算类型大小且返回值为void*,new/delete可自己计算类型的大小对应类型的指针。

5.new/delete的底层调用了malloc/free。

6.malloc/free申请空间后得判空,new/delete则不需要。

7.new直接跟类型,malloc跟字节数个数。

原文地址:https://www.cnblogs.com/WindSun/p/11445844.html

时间: 2024-10-08 02:43:56

C++内存管理(new operator/operator new/operator delete/placement new)的相关文章

动态内存管理

(1).c中动态内存管理方式 malloc.calloc.realloc在堆上开辟空间.free将申请的空间释放掉 void *malloc( size_t size );      void *calloc( size_t num, size_t size );      void *realloc( void *memblock, size_t size ); (2).C++中动态内存管理 通过new和delete运算符进行动态内存管理 (3).malloc/free和new/delete的

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳.operator new 和 operator delete 实现一个简单内存泄漏跟踪器 一.转换构造函数 可以用单个实参来调用的构造函数定义从形参类型到该类型的一个隐式转换.如下: class Integral { public: Integral (int = 0); //转换构造函数 private: int real; }; Integral A = 1; //调用转换构造函数将1转换为In

C++面向对象高级编程(九)Reference与重载operator new和operator delete

摘要: 技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一 Reference 引用:之前提及过,他的主要作用就是取别名,与指针很相似,实现也是基于指针. 1.引用必须有初值,且不能引用nullptr 2.引用之后不能再引用别人 3.引用通常不用于声明变量,多用于参数类型,和返回值类型 见下面代码 int main(int argc, const char * argv[]) { int x=0; //p is a pointer to x int* p = &x; // r is a

条款八: 写operator new和operator delete时要遵循常规

自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致.实际做起来也就是:要有正确的返回值:可用内存不够时要调用出错处理函数(见条款7):处理好0字节内存请求的情况.此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题. 有关返回值的部分很简单.如果内存分配请求成功,就返回指向内存的指针:如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常.operator new实际上会不

c++ 块内存管理

#pragma once   //头文件 #include<iostream> #include<string.h> #include<list> #include "TypeTraits.hpp"  //类型萃取 #include <stdarg.h> using namespace std; struct SaveAdapter//保存适配器 基类 { virtual void save(const char* fmt, ...) =

(转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对 C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃 C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能

浅谈C++容器动态内存管理的优化

在信息学竞赛中,C++的容器的用途非常广泛,但经常因常数过大而超时.怎样才能提高它们的效率呢? 我们知道,容器是存储同一类对象的对象,既然"对象"我们无法改变,那么我们只能从"存储"入手,不难想到,不同容器在实现上的根本区别是它们对应着不同的内存组织方式,内存管理无疑是这种实现的核心,所以优化内存管理是加快容器效率的最好途径之一. 一.内存分配器简介 怎样才能优化内存管理呢?很简单,C++为我们提供了这样的接口,我们可以通过自定义容器模板中的最后一个allocato

C++内存管理(超长,例子很详细,排版很好)

[导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能.本期专题将从内存管理.内存泄漏.内存回收这三个方面来探讨C++内存管理问题

C++内存管理(超长)

[导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能.本期专题将从内存管理.内存泄漏.内存回收这三个方面来探讨C++内存管理问题