C++ 中的内存分配 -- new 与 delete

c++ 常用的内存分配
分配释放类别是否可以重载
mallocfreeC
newdeleteC++ 表达式(expressions)
operator new()operator delete()c++ 函数
operator new[]operator delete[]c++ 函数(用于数组)
allocator<T>::allocateallocator<T>::deallocatec++ 标准库可以自由设计,并应用于各容器

一   malloc 与 free

     malloc 与 free 是 C语言中用于分配内存与释放内存的两个函数,两者配对使用。

malloc 函数的函数原型为:void* malloc(unsigned int size),它根据参数指定的尺寸来分配内存块,并且返回一个void型指针,指向新分配的内存块的初始位置。如果内存分配失败(内存不足),则函数返回NULL。

free 函数原型为:void free (void* ptr),用于将 malloc 分配的内存释放掉

#include<stdio.h>
#include<malloc.h>

int main()
{
     int SIZE = 10;
    // 分配三个 int 内存的空间
    int* ptr = (int*)malloc(sizeof (int) * SIZE);

    if(ptr == NULL)
    {
        printf("failed allocate. \n");
        exit(1);
    }

    // 赋值
    for(int i = 0; i < SIZE; i++)
    {
        ptr[i] = i;
    }

    // 打印
    for(int i = 0; i < SIZE; i++)
    {
        printf("%d  ", ptr[i]);
    }

    // 释放内存
    free(ptr);

    return 0;
}

这里有一个问题:

      我们调用 malloc 后,只返回了一个指针,那么 free 函数如何知道 malloc 分配了多大的内存, free 以指针为起点释放掉多大的内存呢?

      为了解决这个问题,内存管理提供了 cookie 机制。实际上,malloc分配的内存会在内存的起始与结尾带上有 cookie。

参考:

动态内存分配(malloc)详解-CSDN博客

C++ 中malloc函数详解(转载)_c++中void* __cdecl-CSDN博客

浅谈malloc()与free() - 知乎 (zhihu.com)

malloc和free的实现原理解析 - 知乎 (zhihu.com)

C++内存管理(malloc和free中的cookie) - 知乎 (zhihu.com)

二  operator  new 与 operator  delete

现在有这样一个 class Complex:

#include<iostream>

class Complex
{
public:
    Complex():x(0),y(0)
    {
        std::cout << "Complex consturctor." << std::endl;
    }

    ~Complex()
    {
        std::cout << "Complex desturctor." << std::endl;
    }

    // 用于给单个对象分配内存
    void* operator new(std::size_t size)
    {
        std::cout << "Foo operator new size:  " << size << std::endl;
        return malloc(size);
    }
    void operator  delete(void* ptr)
    {
        std::cout << "Foo operator delete " << std::endl;
        return free(ptr);
    }

    // 用于给一组对象分配内存
    void* operator new[](std::size_t size)
    {
        std::cout << "Foo operator new[] size:  " << size << std::endl;
        return malloc(size);
    }
    void  operator delete[](void* ptr)
    {
        std::cout << "Foo operator delete[] " << std::endl;
        return free(ptr);
    }

    // 标准库提供的 placement new 的重载形式
    void* operator new(std::size_t size, void* ptr)
    {
        std::cout << "Foo operator operator new(std::size_t size, void* ptr), size: " << size << std::endl;
        return ptr;
    }
    void  operator delete(void* ptr1, void* ptr2)
    {
        std::cout << "Foo::operator delete(void*, void*)."<< std::endl;
    }

private:
    int x;
    int y;
};

那么在执行 new 与 delete 两个 expression 时,内部发生了什么呢?(面试常问)

// new expression
Complex* ptr = new Complex;

// 在编译器中等同于下面大括号内
{

Complex* ptr;

try {
  void*  mem =  operator new(size);  // 1. 分配内存
  ptr = static_case<Complex*>(mem);  // 2. 指针类型转换
  ptr->Complex::Complex();           // 3. 执行构造函数,只有编译器可以这样使用
} 
cache(std::bad_alloc)
  // 若是分配内存阶段出错,则不再执行 构造函数
}

// delete expression
delete ptr;
// 在编译器中等同于下面大括号内
{
   ptr->~Complex();    // 1. 执行析构函数
   operator delete(ptr);        // 2. 释放内存
}

程序验证如下:

#include"complex.h"

int main()
{
    // 输出 class 大小
    std::cout << "sizeof(Complex): " << sizeof(Complex) << std::endl;
    // 1. 单个对象
    Complex* comPtr = new Complex;

    delete comPtr;
    return 0;
}

输出结果如下:

总结一下:

 1. new expression 申请内存的步骤

   1.1  调用 operator new 函数分配目标类型大小的内存空间,而 operator new 函数内存实际上调用的是 malloc 函数分配的内存

   1.2  将申请到的内存块,由 void* 指针类型 强制转换为目标类型的指针

   1.3  通过指针调用目标类的构造函数(只有编译器可以只有直接调用构造函数) 

  2. delete expression 释放内存的步骤

   2.1 通过指针调用目标类的析构函数

   2.2  调用 operator delete 释放对象内存,而 operator delete 内部实际是通过 free 函数释放分配的内存

三  operator new[] 与 operator  delete[]

那么在执行 new[ ] 与 delete 两个 expression[ ] 时,内部发生了什么呢?(面试常问)

// new[ ] expression
Complex* ptr = new Complex[3];

// 等同于下面大括号内
{
Complex* ptr;

try {
  void*  mem =  operator new[](size * sizeof(Complex)); // 1. 分配内存
  ptr = (Complex*)mem;                                  // 2. 指针类型转换
  ptr->Complex::Complex();                              // 3. 执行 3 次构造函数,从下标 i = 0, 1, 2 依次执行构造函数,只有编译器可以这样使用                
} 
cache(std::bad_alloc)
  // 若是分配内存阶段出错,则不再执行 构造函数
}

// delete[ ] expression
delete ptr;
// 等同于下面大括号内
{  
   ptr->~Complex();               // 1. 执行 3 次析构函数,从下标 i = 2, 1, 0 依次执行析构函数
   operator delete[](ptr);        // 2. 释放内存
}

下面来验证一下内部发生的过程,先定义一下 class Complex

#include<iostream>

class Complex
{
public:
    Complex():x(0),y(0)
    {
        std::cout << "Complex default consturctor. this = " << this << std::endl;
    }

    Complex(int x, int y):x(x),y(y)
    {
        std::cout << "Complex consturctor. this = "<< this <<" x: "<< x <<", y: "<< y << std::endl;
    }

    ~Complex()
    {
        std::cout << "Complex desturctor. this = "<< this <<" x: "<< x <<", y: "<< y << std::endl;
    }

    // 用于给单个对象分配内存
    void* operator new(std::size_t size)
    {
        std::cout << "Foo operator new size:  " << size << std::endl;
        return malloc(size);
    }
    void operator  delete(void* ptr)
    {
        std::cout << "Foo operator delete " << std::endl;
        return free(ptr);
    }

    // 用于给一组对象分配内存
    void* operator new[](std::size_t size)
    {
        std::cout << "Foo operator new[] size:  " << size << std::endl;
        return malloc(size);
    }
    void  operator delete[](void* ptr)
    {
        std::cout << "Foo operator delete[] " << std::endl;
        return free(ptr);
    }

    // 标准库提供的 placement new 的重载形式
    void* operator new(std::size_t size, void* ptr)
    {
        std::cout << "Foo operator operator new(std::size_t size, void* ptr), size: " << size << std::endl;
        return ptr;
    }
    void  operator delete(void* ptr1, void* ptr2)
    {
        std::cout << "Foo::operator delete(void*, void*)."<< std::endl;
    }

private:
    int x;
    int y;
};

验证程序:

#include"complex.h"

int main()
{
    // 输出 class 大小
    std::cout << "sizeof(Complex): " << sizeof(Complex) << std::endl;
    // 1. 单个对象
    std::cout << "---new[] expression---" << std::endl;
    Complex* comPtr = new Complex[3];

    Complex* tmpPtr = comPtr;
    // placement new
    for(int i = 0; i < 3; i++)
    {
        new(tmpPtr++)Complex(i, i);
    }

    std::cout << "---delete[] expression---" << std::endl;
    delete[] comPtr;
    
    return 0;
}

输出:

从输出结果的构造函数的地址来看,地址是依次递增的,而执行析构函数时,地址正好是反回来的,说明是构造对象执行构造函数的顺序,与执行对象的析构函数的顺序是反过来的。

验证程序中在事先分配好的内存上,调用 placement new ,在已有的内存上构造对象。

总结

1. new[] expression 申请内存的步骤

   1.1  调用 operator new[] 函数分配目标类型大小的内存空间,而 operator new[] 函数内存实际上调用的是 malloc 函数分配的内存, 如:想要分配 size 个 Demo 类对象大小的内存,那么内存大小最终为 size * sizeof(Demo)。

   1.2  将申请到的内存块,由 void* 指针类型 强制转换为目标类型的指针

   1.3  通过指针依次调用 size 个目标类的构造函数(只有编译器可以只有直接调用构造函数) 

          调用顺序下标:从 0 , 1, ..., size - 1

  2. delete[] expression 释放内存的步骤

   2.1 通过指针依次调用 size 个目标类的析构函数,与构造函数的调用顺序正好相反,调用顺序下标:从 size - 1 , ..., 1 , 0

   2.2  调用 operator delete 释放对象内存,而 operator delete 内部实际是通过 free 函数释放分配的内存

四  placement new

  placement new 允许我们将对象构建与已分配好的内存上,没有所谓的 placment delete ,因为压根也没有专门为 placement 分配过内存。

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

Complex* ptr = new(buf)Complex(0,0);    // 2. 在已分配的内存上构造对象 

delete [] buf;                          // 3. 释放内存

// 上面的 2 步在编译器中等同于下面
Complex* ptr;
try
{
   void*  ptr = operator new(sizeof(Complex), buf); // 1. 实际上就是将char* 指针转为 void*
   ptr = static_cast<Complex*>(ptr);                // 2. 将 void* 指针强转为 Complex*
   ptr->Complex::Complex();                         // 3. 利用 Complex* 指针调用构造函数
}
cache(std::bad_alloc)
{
   // 若 allocate 分配失败,则 不再执行构造函数
}

 验证程序如下:

#include"complex.h"

int main()
{
    char* buf = new char[sizeof (Complex)];

    Complex* ptr = new(buf)Complex(0,0);

    delete [] buf;
    return 0;
}

输出:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137628.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ConstraintLayout的基本用法

ConstraintLayout的基本用法 1、基线对齐——Baseline 有时候我们需要这样一个场景&#xff1a; app:layout_constraintBaseline_toBaselineOf"id/30"2、链——Chains 用于将多个控件形成一条链&#xff0c;可以用于平分空间。 <?xml version"1.0"…

Think-on-Graph:基于知识图的大型语言模型的深层可靠推理11.12

Hink-on-Graph&#xff1a;基于知识图的大型语言模型的深层可靠推理 摘要1 引言2 方法2.1图上思考2.1.1图的初始化2.1.2 探索2.1.3推理 2.2 基于关系的Think on graph 摘要 尽管大型语言模型&#xff08;LLM&#xff09;在各种任务中取得了巨大的成功&#xff0c;但它们经常与…

python类中的抽象函数,以及继承后子类的比较

抽象函数的定义方式 导包 from abs import ABCMeta,abstractmethod声明抽象类 class Area(object):abstractmethoddef area(self):pass在抽象类中&#xff0c;不用写构造函数&#xff0c;抽象类不能进行实例化 继承抽象类的子类必须将抽象类中的函数进行重写&#xff08;不重…

【Android】Android apk 逆向编译

链接&#xff1a;https://pan.baidu.com/s/14r5s9EJwQgeLK5cCb1Gq1Q 提取码&#xff1a;qdqt 解压jadx 在 lib 文件内找到 jadx-gui-1.4.7.jar 打开cmd 执行 &#xff1a;java -jar jadx-gui-1.4.7.jar示列&#xff1a;

数据代理机制

目录 前言 Object.defineProperty() 语法 第三个参数配置项 数据代理机制的实现 MVVM分层思想 前言 本文介绍Vue的数据代理机制&#xff0c;也就是通过vue实例对象来代理data对象中的属性的操作 Object.defineProperty() 在介绍vue的数据代理机制前&#xff0c;我们需要…

LLM 面试总结

溜一遍 MLStack.Cafe - Kill Your Next Machine Learning & Data Science Interview https://www.llmforce.com/llm-interview-questions MLStack.Cafe - Kill Your Next Machine Learning & Data Science Interview An interview with a language model, ChatGPT - W…

大数据技术与原理实验报告(MapReduce 初级编程实践)

MapReduce 初级编程实践 验环境&#xff1a; 操作系统&#xff1a;Linux&#xff08;建议Ubuntu16.04&#xff09;&#xff1b; Hadoop版本&#xff1a;3.2.2&#xff1b; &#xff08;一&#xff09;编程实现文件合并和去重操作 对于两个输入文件&#xff0c;即文件 A 和…

Spark Job优化

1 Map端优化 1.1 Map端聚合 map-side预聚合&#xff0c;就是在每个节点本地对相同的key进行一次聚合操作&#xff0c;类似于MapReduce中的本地combiner。map-side预聚合之后&#xff0c;每个节点本地就只会有一条相同的key&#xff0c;因为多条相同的key都被聚合起来了。其他节…

pychon/PIL/opencv/json学习过程中遇到的问题

1. 使用PIL.Image读取图片 注意&#xff1a;pytorch中对图像预处理是transforms的输入必须是PIL格式的文件&#xff0c;使用cv2读取的图片就按照第二条的代码处理&#xff08;3通道合并、归一化处理&#xff09; from PIL import Image img Image.open("test1.jpg"…

数据结构 队列(C语言实现)

目录 1.队列的概念及结构2.队列的代码实现 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 1.队列的概念及结构 队列&#xff1a;只允许在…

【多线程 - 03、线程的生命周期】

生命周期 当线程被创建并启动以后&#xff0c;它不是一启动就进入执行状态&#xff0c;也不会一直处于执行状态&#xff0c;而是会经历五种状态。 线程状态的五个阶段&#xff1a; 新建状态&#xff08;New&#xff09;就绪状态&#xff08;Runnable&#xff09;运行状态&…

【c++随笔12】继承

【c随笔12】继承 一、继承1、继承的概念2、3种继承方式3、父类和子类对象赋值转换4、继承中的作用域——隐藏5、继承与友元6、继承与静态成员 二、继承和子类默认成员函数1、子类构造函数 二、子类拷贝构造函数3、子类的赋值重载4、子类析构函数 三、单继承、多继承、菱形继承1…

MyBatis研究

入门级使用 参照MyBatis官网的简介与入门部分&#xff0c;尝试使用MyBatis&#xff0c;可创建新的Maven项目&#xff0c;引入以下依赖&#xff1a; <dependencies> <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</…

Spark 资源调优

1 资源规划 1.1 资源设定考虑 1、总体原则 以单台服务器128G内存&#xff0c;32线程为例。 先设定单个Executor核数&#xff0c;根据Yarn配置得出每个节点最多的Executor数量&#xff0c;每个节点的yarn内存/每个节点数量单个节点的数量 总的executor数单节点数量*节点数。 2、…

C/C++满足条件的数累加 2021年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C满足条件的数累加 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C满足条件的数累加 2021年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 现有n个整数&#xff0c;将其中个位数…

react 组件进阶

目标&#xff1a;1.能够使用props接收数据 2.能够实现父子组建之间的通讯 3.能够实现兄弟组建之间的通讯 4.能够给组建添加props校验 5.能够说出生命周期常用的钩子函数 6.能够知道高阶组件的作用 一&#xff0c;组件通讯介绍 组件是独立且封闭的单元&#xff0c;默认情况下&a…

U-Mail邮件中继,让海外邮件沟通更顺畅

在海外&#xff0c;电子邮件是人们主要的通信工具&#xff0c;尤其是商务往来沟通&#xff0c;企业邮箱是标配。这主要是因为西方国家互联网发展较早&#xff0c;在互联网早期&#xff0c;电子邮件技术较为成熟&#xff0c;大家都用电子邮件交流&#xff0c;于是这成了一种潮流…

2022年03月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 下面有关random的常用方法,描述错误的是? A: random.random()生成一个[0.0,1.0)之间的随机小数 B: random.randint(a,b)生成一个[a,b]之间的随机整数 C: random.choice(seq)从序列中…

SpringBoot2.X整合集成Dubbo

环境安装 Dubbo使用zookeeper作为注册中心&#xff0c;首先要安装zookeeper。 Windows安装zookeeper如下&#xff1a; https://blog.csdn.net/qq_33316784/article/details/88563482 Linux安装zookeeper如下&#xff1a; https://www.cnblogs.com/expiator/p/9853378.html Sp…

高防CDN:护航网络安全的卓越之选

在当今数字化时代&#xff0c;网络攻击与日俱增&#xff0c;为了确保网站和应用程序的稳定运行&#xff0c;高防CDN&#xff08;高防御内容分发网络&#xff09;应运而生。选择高防CDN的理由不仅源于其强大的防护性能&#xff0c;还体现了其与硬件防火墙异曲同工的奥妙。 选择高…