返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >一篇文章了解c++中的new和delete
  • 843
分享到

一篇文章了解c++中的new和delete

2024-04-02 19:04:59 843人浏览 八月长安
摘要

目录new expressiondelete expressionnew[]和new()new[]和delete[]new的内存分布placement newnew失败处理捕捉异常禁

new expression

new一个类型,会创建一个该类型的内存,然后调用构造函数,最后返回该内存的指针

注意:该操作是原子性的。

在vc6中的实现如下


void *operator new(size_t size, const std::nothrow_t &) _THROW0()
{
    void *p
    while((p = malloc(size)) == 0)
    {
        // 如果调用malloc失败后会调用_callnewh
        // _callnewh含义是call new handler,简单说就是用户设定一个回调函数
        // 使用_set_new_handler来设置,通常是用户自己控制释放一些不用的内存
        _TRY_BEgit
            if(_callnewh(size) == 0) break;
        _CATCH(std::bad_alloc) return (0);
        _CATCH_END
    }
    return (p);
}

delete expression

delete 一个指针,先调用析构函数,然后释放内存

在vc6中的实现如下


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

new[]和new()

new[]是分配指针数组,new()是分配时直接初始化,这两个很容易搞混,关键是编译都能过,一定要注意。比如:


int *p = new[3]; // 是分配三个int*指针所组成的指针数组
int *q = new(3); // 是分配一个int堆内存,并初始化为3

new[]和delete[]

Complex *pca = new Complex[3];

调用三次Complex的构造函数,分配三个Complex对象

delete[] pca;

释放内存。

如果这里的delete[]只写写成delete会怎么样?好多人一定会说:会内存泄露。

其实正确的答案是不确定,具体需要看Complex类的内部有没有堆内存

new[]后内存是怎么样的呢?看下图

关键是看图中的cookie部分,存放了一些内存相关的数据,其中最关键的是在cookie中存放了分配内存的大小

再来看一下下面的代码


string *psa = new string[3];
delete psa;

执行完该代码后内存分配如下

由于string类的内部使用动态堆内存来保存字符串,new[]分配的内存的cookie只记录了string类的信息,而类内部的动态堆内存信息由每个实例自行管理,不在new[]的cookie中。

前面说过,delete释放内存的过程是先调用析构函数,再释放内存。在本例中,如果使用delete[]来释放内存,会依次调用每个实例的析构函数,每个析构函数会自行释放自己内部的堆内存,然后在释放new的内存块。但是如果使用delete来释放内存,只会是第一个实例调用一次析构函数,另外两个实例不会调用,然后根据cookie中记录的内存大小释放有new分配的内存,另两个实例中的堆内存就泄露了。

也就是说,对于上图string的例子,如果使用delete直接释放内存,泄露的是str2和str3箭头右边的白色区域所示的内存,而pas箭头右边的绿色区域是能够正确释放的(具体是调用的str1还是str3取决于编译器的具体实现,理解意思即可)。

但是,这不意味着你可以在类内部没有堆内存的情况下就可以毫无顾忌的使用delete来释放new[],这是编码规范的问题,使用delete不一定有错,但使用delete[]则是一定没错。

new的内存分布

下图是vc6中new的内存布局

我们得到的是图中0x00441c30这一部分的指针,但实际上内存管理的是图中所有的一大块内存,其中橘黄色部分只有在debug模式下才有。由于内存管理需要是16的倍数,如果不够16的倍数,则添加一些数据凑到16的倍数,图中蓝色的pad部分就是添加的无用数据。图中61h部分就是cookie,上下部分分别为上cookie和下cookie。由于本例使用的是int类型举例,而int没有析构不析构的,所以可以直接使用delete就能完整释放整块内存。这里这么写是为了让读者加深理解,实际编码的时候要加上[],这里对比一下下图

这张图使用的类型是一个类,用new[]分配内存的时候,返回的指针和调试信息中间多出来一块内存用来记录实例的个数,就是图中的3。这中情况,如果使用delete[]来释放内存,会正确索引到实例的首地址进行释放操作,如果使用delete来释放内存,索引到的内存是记录实例个数的整型数据位置,如果从这里开始按找该类的内存结构进行析构,肯定是会出问题的,整个内存结构都乱了。

这里有个地方需要注意,这里的delete和delete[]部分看起来和new[]和delete[]小结中介绍的有些矛盾,老师是怎么讲的,由于是看的盗版网课,也没办法请教老师,具体是怎么情况我也不太清楚。猜测是因为不同编译器具体实现时,3的位置不同,有的在前面,有的在后面,关键是看具体实现,在前面的情况就是矛盾的,在后面就没事,关键是领会精神。

placement new

placement new 允许我们将对象构建于一个已经分配的内存当中

没有所谓的placement delete,因为placement根本就没有分配内存,它只是使用了一个已经分配好的内存,所以不需要配套的释放操作,具体用法如下


#include <new>

// 分配内存
char *buf = new char[sizeof(Complex) * 3];

// 在分配好的内存上构造Complex
Complex *pc = new(buf)Complex(1, 2);

// 注意这里要释放的指针
// 感觉如果直接释放pc应该也没错
// 手上没环境不能测试,以后有时间测一下
delete[] buf;

new失败处理

在纯C中使用malloc来分配内存,需要判断一下返回的指针,如果返回一个空指针,则代表内存分配失败。

到了c艹中,使用new来分配内存,则无法通过判断空指针的方法判断是否失败。因为在c艹中,如果new失败会抛出异常,代码是走不到判断空指针的语句的。new失败正确处理方法有以下几种

捕捉异常


try 
{
    int* p = new int[SIZE];
    // 其它代码
} catch ( const bad_alloc& e ) 
{
    return -1;
}

据说古老的c++编译器new失败不会抛异常,而是和malloc一样返回空指针,因为那时候c++还没有异常机制,坊间流传,也懒得考证,了解以下即可。顺便吐槽一下,说c艹的异常是屎,这是对屎的侮辱,屎还能当肥料种地呢,c艹的异常除了捣乱没任何鸟用。

禁用new的异常


 int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针

new-handler

文章开始介绍new源码的时候提到过,new实现的时候会调用new-handler的回调函数,在new之前设置好回调函数即可。由于此方法太过麻烦,懒得研究,具体用法读者自行查找相关资料。

重载

重载的时候,一般不重载全局的::operator new,因为全局的影响太大,一般只重载类自身的Foo::operator new。

重载一般在内存池中用的比较多,可以减少cookie

重载全局的::operator new


void *myAlloc(size_t size)
{ return malloc(size); }

void myFree(void *p)
{ free(p); }

// 下面代码实现部分不重要,关键看接口的重载
// 它们不可以被声明在一个namespace内
inline void *operator new(size_t size)
{ cout << "global new()\n"; return myAlloc(size); }

inline void *operator new[](size_t size)
{ cout << "global new[]()\n"; return myAlloc(size); }

inline void operator delete(void *p)
{ cout << "global delete()\n"; return myFree(p); }

inline void operator delete[](void *p)
{ cout << "global delete[]()\n"; return myFree(p); }

重载局部的Foo::operator new


class Foo
{
public:
    void *operator new(size_t);
    void operator delete(void*);
};

需要注意的是,重载局部的new和delete必须是static的,因为new调用时是内存对象创建过程当中,此时还没有一个完整的内存对象,无法通过对象来调用一般的函数。由于必须是static的,不管写不写static,编译器都会当成是static处理。

数组版本也是一样的,只是都加了一个[],这里就不再写一次了

重载placement new

placement new的括号中不一定非要放指针,我们可以自己来定义放任意的东西。放指针的版本是标准库中先写好给我们用的,我们也可以通过重载placement new来自定义所放的数据,比如Foo *pf = new(300, 'c')Foo;。可以重载为多种参数形式,但多个重载的参数列形式不能重复,必须满足普通函数重载的条件。其中第一个参数必须是size_t,用来传递类的大小,该参数类似于成员函数的this指针,在调用时自动传递,不需要显示传递。比如在Foo *pf = new(300, 'c')Foo;中,其声明形式为void *operator new(size_t, int, char);。如果内存不是外部申请好的,需要在placement new函数内部去申请内存。

重载new的时候应该对应重载一个相同形式的delete。但重载placement delete时需要注意,只有在placement new中产生异常,才会调用其对应的placement delete函数。c++这么设计的原因是,在调用placement new函数后,如果内存是由在placement new内申请的,在调用构造函数时如果发生了异常,可以在对应的在placement delete函数中将在placement new中申请的内存释放掉。

如果没有对应形式的delete,编译器也不会报错,编译器会认为你放弃处理该形式的new中产生的异常(个别编译器会给个警告)


class Foo
{
public:

    // 重载一个一般形式的operator new
    void *operator new(size_t);

    // 标准库中placement new的重载形式
    void *operator new(size_t, void *);

    // Foo *pf = new(300, 'c')Foo;调用形式的重载方式
    void *operator new(size_t, int, char);

    // 随便写的一种重载形式
    void *operator new(size_t, size_t, char *, int);

    // 以下是对应的delete
    void *operator delete(void *, size_t);
    void *operator delete(void *, void *);
    void *operator delete(void *, int, char);
    void *operator delete(void *, size_t, char *, int);
};

std::string中就是一个很好的placement new重载,有兴趣的朋友可以去看string的源码

c++ new与malloc的10点差别表格:

特征 new/delete malloc/free
分配内存的位置 自由存储区
内存分配失败返回值 完整类型指针 void*
内存分配失败返回值 默认抛出异常 返回NULL
分配内存的大小 由编译器根据类型计算得出 必须显式指定字节数
处理数组 有处理数组的new版本new[] 需要用户计算数组的大小后进行内存分配
已分配内存的扩充 无法直观地处理 使用realloc简单完成
是否相互调用 可以,看具体的operator new/delete实现 不可调用new
分配内存时内存不足 客户能够指定处理函数或重新制定分配器 无法通过用户代码进行处理
函数重载 允许 不允许
构造函数与析构函数 调用 不调用

malloc给你的就好像一块原始的土地,你要种什么需要自己在土地上来播种

而new帮你划好了田地的分块(数组),帮你播了种(构造函数),还提供其他的设施给你使用:

当然,malloc并不是说比不上new,它们各自有适用的地方。在C++这种偏重OOP的语言,使用new/delete自然是更合适的。

总结

到此这篇关于c++中new和delete的文章就介绍到这了,更多相关c++中new和delete内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 一篇文章了解c++中的new和delete

本文链接: https://lsjlt.com/news/159557.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 一篇文章了解c++中的new和delete
    目录new expressiondelete expressionnew[]和new()new[]和delete[]new的内存分布placement newnew失败处理捕捉异常禁...
    99+
    2024-04-02
  • 一篇文章带你了解C++中的异常
    目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的...
    99+
    2024-04-02
  • C++的new和delete详解
    目录1、new和delete的内部实现2、placement技术3、new和delete运算符重载4、对象的自动删除技术1、new和delete的内部实现 C++中如果要在堆内存中创...
    99+
    2024-04-02
  • 一篇文章带你了解C++中的显示转换
    目录总结命名的强制类型转换: 形式: cast-name<type>(expression); type是强制转换的类型,expression是强制转换的值。如果...
    99+
    2024-04-02
  • 一篇文章带你了解C++的KMP算法
    目录KMP算法步骤1:先计算子串中的前后缀数组NextC++代码:步骤2:查找子串在母串中出现的位置。总结KMP算法 KMP算法作用:字符串匹配 例如母串S = “aaagoogle...
    99+
    2024-04-02
  • 一篇文章带你了解C/C++的回调函数
    目录函数指针概念先来看一个Hello World程序然后,采用函数调用的形式来实现用函数指针的方式来实现函数指针数组回调函数概念标准Hello World程序将它修改成函数回调样式修...
    99+
    2024-04-02
  • c++中的new和delete怎么用
    小编给大家分享一下c++中的new和delete怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!new expressionnew一个类型,会创建一个该类型的...
    99+
    2023-06-22
  • 一篇文章弄懂C#中的async和await
    目录前言 async await 从以往知识推导 创建异步任务 创建异步任务并返回Task 异步改同步 说说 await Task 说说 async Task<TResult&...
    99+
    2024-04-02
  • 一篇文章带你了解Python中的类
    目录1、类的定义2、创建对象3、继承总结1、类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类。在该类中,__init__表示构造方法。其中,s...
    99+
    2024-04-02
  • 一篇文章了解MySQL的group by
    准备工作! 本文章MySQL使用的是5.7,引擎使用的是innodb 2. 使用的表结构(t1),字段a上有一个索引, 1. group by常用方法: group by的常规用法是配合聚合函数,利用分组信息进行统计,常见的是配合max等...
    99+
    2023-08-18
    mysql 数据库 java
  • 一篇文章带你了解C++(STL基础、Vector)
    目录STL基本概念STL六大组件STL中容器、算法、迭代器容器算法迭代器初识Vector容器Vector三大遍历算法Vector存放其他数据类型 Vector容器嵌套总结S...
    99+
    2024-04-02
  • 一篇文章带你了解C语言的文件操作
    目录为什么使用文件什么是文件程序文件数据文件文件名文件的打开和关闭文件指针fopen和fclose函数文件的顺序读写总结为什么使用文件 我们在想既然是通讯录就应该把信息记录下来,只有...
    99+
    2024-04-02
  • 一篇文章带你了解C++特殊类的设计
    目录设计一个类,只能在堆上创建对象设计一个类,只能在栈上创建对象设计一个类,不能被拷贝设计一个类,不能继承设计一个类,只能创建一个对象(单例模式)单例模式的概念单例模式的实现饿汉模式...
    99+
    2024-04-02
  • 一篇文章带你了解C语言中volatile关键字
    目录C语言中volatile关键字总结C语言中volatile关键字 volatile关键字是C语言中非常冷门的关键字,因为用到这个关键字的场景并不多。 当不用这个关键字的时候,CP...
    99+
    2024-04-02
  • 一篇文章带你了解c++运算符重载
    目录友元函数重载:复合赋值Operator pairings自增自减运算符的重载c++20,spaceship operator总结友元函数 一种全局函数,可以在类里声明,其他地方定...
    99+
    2024-04-02
  • 一篇文章带你了解C语言操作符
    目录一、操作符分类 二、算术操作符三、移位操作符1、左移操作符 2、右移操作符2.1算术移位 2.2逻辑移位 四、位操作符 1、按位...
    99+
    2024-04-02
  • 一篇文章带你了解C++智能指针详解
    目录为什么要有智能指针?智能指针的使用及原理RALLshared_ptr的使用注意事项创建多个 shared_ptr 不能拥有同一个对象shared_ptr 的销毁shared_pt...
    99+
    2024-04-02
  • 一篇文章带你了解C++模板编程详解
    目录模板初阶泛型编程函数模板函数模板概念函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则类模板类模板的定义格式类模板的实例化总结模板初阶 泛型编程 在计算机程序设计领域...
    99+
    2024-04-02
  • 深入理解C语言的new[]和delete[]
    目录1、重载操作符2、new和delete的原理3、new[]和delete[]的原理总结c++的动态内存管理方式和c语言不一样,在c++中使用new和delete来替换c语言中的m...
    99+
    2024-04-02
  • C++的new和delete使用示例详解
    目录1. new 和 delete 三种 new2. operator new 和 operator delete3. 重载 operator new 和 operator...
    99+
    2022-12-08
    C++ new和delete使用 C++ new delete
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作