千家信息网

C++中new和delete怎么用

发表于:2024-10-15 作者:千家信息网编辑
千家信息网最后更新 2024年10月15日,这篇文章给大家分享的是有关C++中new和delete怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。new和delete称作运算符我们转反汇编看看这2个运算符本质也是
千家信息网最后更新 2024年10月15日C++中new和delete怎么用

这篇文章给大家分享的是有关C++中new和delete怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

new和delete称作运算符

我们转反汇编看看

这2个运算符本质也是相应的运算符的重载的调用

malloc和new的区别?

1.malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10]
所以malloc开辟内存返回的都是void*
而new相当于运算符的重载函数 operator new ->返回值自动转成指定的类指针 int*
2.malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化

new int(20);//初始化20  new int[20]();//开辟数组是不支持初始化值的,但是支持写个空括号,表示给每个元素初始化为0 ,相当于每个元素调用int()成为0

3.malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
(也就是说,new运算符开辟内存失败,要把它的代码扩在try catch里面,是不能通过返回值和空指针比较的。)

try//可能发生错误的代码放在try里面         {                int *p = new int;                delete []p;                int *q = new int[10];                delete q;        }        catch (const bad_alloc &err)//捕获相应类型的异常         {                cerr << err.what() << endl;//打印错误         }

free和delete的区别?

delete p: 调用析构函数,然后再free( p),相当于包含了free
如果delete的是普通的指针,那么delete (int*)p和free( p)是没有区别的
因为对于整型指针来说,没有析构函数,只剩下内存的释放

new -> 对operator new重载函数的调用 delete -> 对operator delete重载函数的调用

把new和delete的重载函数定义在全局的地方,这样我们整个项目工程中只有涉及到new和delete的地方都会调用到我们全局重写的new,delete的重载函数。

//先调用operator new开辟内存空间、然后调用对象的构造函数(初始化)void* operator new(size_t size){        void *p = malloc(size);        if (p == nullptr)                throw bad_alloc();        cout << "operator new addr:" << p << endl;        return p;}//delete p; 先调用p指向对象的析构函数、再调用operator delete释放内存空间void operator delete(void *ptr){        cout << "operator delete addr:" << ptr << endl;        free(ptr);}


new和delete从内存管理的角度上来说和malloc和free没有什么区别
除非就是内存开辟失败,返回不一样

void* operator new[](size_t size){        void *p = malloc(size);        if (p == nullptr)                throw bad_alloc();        cout << "operator new[] addr:" << p << endl;        return p;}void operator delete[](void *ptr){        cout << "operator delete[] addr:" << ptr << endl;        free(ptr);}


C++中,如何设计一个程序检测内存泄漏问题?
内存泄漏就是new操作没有对应的delete,我们可以在全局重写上面这些函数,在new操作里面用映射表记录都有哪些内存被开辟过,delete的时候把相应的内存资源删除掉,new和delete都有对应关系
如果整个系统运行完了,我们发现,映射表记录的一些内存还没有被释放,就存在内存泄漏了! 我们用new和delete接管整个应用的所有内存管理 ,对内存的开辟和释放都记录
也可以通过编译器既定的宏和API接口,把函数调用堆栈打印出来,到底在哪个源代码的哪一页的哪一行做了new操作没有delete

new和delete能混用吗?

C++为什么区分单个元素和数组的内存分配和释放呢?
下面这样操作是否可以???

其实现在对于整型来说,没有所谓的构造函数和析构函数可言,所以这样的代码就只剩下malloc和free的功能,所以底层调用的就是malloc和free

所以,它们现在混用是没有问题的!!!

那什么时候我们才需要考虑这些问题呢?

class Test{public:        Test(int data = 10) { cout << "Test()" << endl; }        ~Test() { cout << "~Test()" << endl; }private:        int ma;};

在这里面,我们能不能混用呢?


出现错误了。
此时new和delete不能进行混用了!


在这里,new和delete可以混用吗?


运行出错了。

我们最好是这样配对使用:

new deletenew[] delete[]

对于普通的编译器内置类型
new/delete[]
new[]/delete
这样混用是可以的!
因为只涉及内存的开辟和释放,底层调用的就是malloc和free

但是,如果是对象,就不能混用了。

一个Test对象是4个字节。
每一个Test对象有1个整型的成员变量。

new的时候,分配了5个Test对象,但是不只是开辟了20个字节哦!
delete[]p2的时候先调用Test对象的析构函数,析构函数有this指针,this指针区分析构的对象,this指针把正确的对象的地址传到析构函数。现在加了[]表示有好几个对象,有一个数组,里面的每个对象都要析构,但是它是怎么知道是有5个对象呢???
所以,实际上,new Test[5]是开辟了如图式的内存:
多开辟了4个字节,存储对象的个数。
用户在写new Test[5]时,这个5是要被记录下来的。
而且,new操作完了之后,给以后返回的p2指针指向的地址是0x104这个地址!即数组首元素的地址。并不是真真正正底层开辟的0x100这个地址,因为那个是不需要让用户知道的,用户只需要知道这个指针指向的是第一个元素对象的地址。

当我们去delete[]p2的时候,它一看这个[]就知道释放的是一个对象数组,那么就要从p2(0x104)上移4个字节,去取对象的个数,知道是5个对象了(一个对象是4字节),然后把ox104下的内存平均分成5份,每一份内存的起始地址就是对象的起始地址,然后传给对象的析构函数,就可以进行对象的析构了。然后进行内存的释放,operator delete(p2-4),从0x100开始释放!!!


这个代码错误在:实际上开辟的内存空间大小是20+4=24字节,开辟内存是从0028开辟的,因为它有析构函数,所以在底层给数组开辟内存时多开辟了4个字节来存储开辟的对象的个数,但是用户返回的是02c,比028刚好多了4个字节,也就是给用户返回的是真真正正对象的起始地址。
delete p2;它就认为p2只是指向1个对象,因为没有使用delete[],所以它就只是把Test[0]这个对象析构了而已,然后直接free(p2),从第一个对象的地址(02c)开始free,而底层内存是从028开始开辟的。

我们换成delete[]p2,来运行看看

从指针-4开始free释放内存的操作

这个代码的出错在:只是new出来1个对象,在0x104开辟的,p1也是指向了0x104,但是在delete[]的时候,认为是指向的是对象数组,因为还有析构函数,于是它就从0x104上移4个字节去取开辟对象的个数,

这就出现了问题了。
关键是它free的时候,执行的是free(0x104-4)
但是new的时候并不是从0x100开始开辟内存的。

自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,会多开辟4个字节,记录对象的个数

对象池代码应用

对象池的实现是静态链表,在堆上开辟的。



#include using namespace std;templateclass Queue{public:        Queue()//构造函数 0构造(默认构造)         {                _front = _rear = new QueueItem();        }        ~Queue()//析构函数         {                QueueItem *cur = _front;//指向头结点                 while (cur != nullptr)                {                        _front = _front->_next;                        delete cur;                        cur = _front;                }        }        void push(const T &val)//入队操作        {                QueueItem *item = new QueueItem(val);//malloc                _rear->_next = item;                _rear = item;        }        void pop()//出队操作 队头出 头删法         {                if (empty())                        return;                QueueItem *first = _front->_next;                _front->_next = first->_next;                if (_front->_next == nullptr)//队列原本只有1个有效元素节点                 {                        _rear = _front;                }                delete first;//free        }        T front()const//获取首元素的值         {                return _front->_next->_data;        }        bool empty()const { return _front == _rear; }//判空  链式队列 private:        //产生一个QueueItem的对象池(10000个QueueItem节点)        struct QueueItem//节点类型,链式队列,带头节点的单链表         {                QueueItem(T data = T()) :_data(data), _next(nullptr) {}//构造函数                                 //给QueueItem提供自定义内存管理                void* operator new(size_t size)                {                        if (_itemPool == nullptr)//如果对象池满了,对象池的指针就指向空了,然后现在进入,再开辟一个对象池                         {                                _itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//开辟池                                 QueueItem *p = _itemPool;                                for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)//连在一个链表上                                 {                                        p->_next = p + 1;//因为节点内存是连续开辟的 可以用p+1                                 }                                p->_next = nullptr;                        }                        QueueItem *p = _itemPool;                        _itemPool = _itemPool->_next;                        return p;                }                void operator delete(void *ptr)                {                        QueueItem *p = (QueueItem*)ptr;                        p->_next = _itemPool;                        _itemPool = p;//往头前放,然后连起来                 }                T _data;//数据域                 QueueItem *_next;//指向下一个节点的指针域                 static QueueItem *_itemPool;//指向对象池的起始地址,因为所有的 QueueItem都放在一个对象池里面                 static const int POOL_ITEM_SIZE = 100000;//开辟的对象池的节点的个数,静态常量可以直接在类体初始化         };        QueueItem *_front;//指向头节点        QueueItem *_rear;//指向队尾 即链表的最后一个元素 };template//在类外定义静态成员变量 typename Queue::QueueItem *Queue::QueueItem::_itemPool = nullptr;//typename告诉编译器后边的嵌套类作用域下的名字是类型,放心使用吧 int main(){        Queue que;        for (int i = 0; i < 1000000; ++i)        {                que.push(i);//QueueItem(i)                que.pop();//QueueItem        }        cout << que.empty() << endl;        return 0;}

可以把指针改为智能指针,出作用域,对象池自动释放

感谢各位的阅读!关于"C++中new和delete怎么用"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

0