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

news2024/11/20 3:32:46

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;
}

输出:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java使用geotools导出shp文件

SHP格式是一种矢量数据格式&#xff0c;用于存储地理信息系统&#xff08;GIS&#xff09;数据。 SHP文件由一系列有序的文件组成&#xff0c;我们导出的shp文件包括.shp、.shx、.dbf、.prj以及.fix文件。 .shp&#xff08;shape&#xff09;文件&#xff1a;存储矢量地图数据&…

Ubuntu 22.04源码安装cmake 3.27.7

安装参考博客是《ubuntu安装cmake》和《Ubuntu 安装CMake》。 https://cmake.org/download是cmake官网下载的网址。 sudo wget -c https://github.com/Kitware/CMake/releases/download/v3.27.7/cmake-3.27.7.tar.gz可以下载源码&#xff0c;最后显示‘cmake-3.27.7.tar.gz’…

详解数据仓库之拉链表(原理、设计以及在Hive中的实现)

最近发现一本好书&#xff0c;读完感觉讲的非常好&#xff0c;首先安利给大家&#xff0c;国内第一本系统讲解数据血缘的书&#xff01;点赞&#xff01;近几天也会安排朋友圈点赞赠书活动(ง•̀_•́)ง 0x00 前言 本文将会谈一谈在数据仓库中拉链表相关的内容&#xff0c;包…

分类网络搭建示例

搭建CNN网络 本章我们来学习一下如何搭建网络&#xff0c;初始化方法&#xff0c;模型的保存&#xff0c;预训练模型的加载方法。本专栏需要搭建的是对分类性能的测试&#xff0c;所以这里我们只以VGG为例。 请注意&#xff0c;这里定义的只是一个简陋的版本&#xff0c;后续一…

基于猕猴感觉运动皮层的神经元Spike信号分析

公开数据集中文版详细描述参考前文&#xff1a;https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192 目录 0. 公开数据集1. 神经元的raster和PSTH图1.1 Raster1.2 PSTH 2. 运动轨迹图 (center_out)3. 神经元的运动调制曲线 (tuning curve) 0. 公开数据集 …

Leetcode100120. 找出强数对的最大异或值 I

Every day a Leetcode 题目来源&#xff1a;100120. 找出强数对的最大异或值 I 解法1&#xff1a;模拟 枚举 2 遍数组 nums 的元素&#xff0c;更新最大异或值。 代码&#xff1a; /** lc appleetcode.cn id100120 langcpp** [100120] 找出强数对的最大异或值 I*/// lc c…

火爆进行中的抖音双11好物节,巨量引擎助5大行业商家开启爆单之路!

抖音双11好物节目前正在火热进行中&#xff0c;进入爆发期&#xff0c;各大商家“好招”频出&#xff0c;都想要实现高速增长。依托“人群、货品、流量”三大优势&#xff0c;巨量引擎一直都是商家生意增长的给力伙伴&#xff0c;在今年的抖音双11好物节&#xff0c;巨量引擎就…

SparkSQL之Catelog体系

按照SQL标准的解释&#xff0c;在SQL环境下Catalog和Schema都属于抽象概念。在关系数据库中&#xff0c;Catalog是一个宽泛的概念&#xff0c;通常可以理解为一个容器或数据库对象命名空间中的一个层次&#xff0c;主要用来解决命名冲突等问题。 在Spark SQL系统中&#xff0c;…

Django基础介绍及HTTP请求

文章目录 Django框架的介绍Django的安装 Django框架开发创建项目的指令Django项目的目录结构URL 介绍视图函数(view)Django 中的路由配置带有分组的路由和视图函数带有命名分组的路由和视图函数 HTTP协议的请求和响应HTTP 请求HTTP 响应GET方式传参POST传递参数form 表单的name…

泉峰控股发布业务白皮书, 释放中国芯片企业要发展成全球领先的信心与决心

近日,多元化芯片全球供应商泉峰控股发布了一份题为《致力于中国芯片产业自立自强》的业务白皮书。白皮书系统阐述了泉峰控股企业发展策略和业务规划,充分体现出中国芯片企业要在全球范围内实现技术突破、市场扩张的信心与决心。 白皮书首先分析了当前全球芯片产业的发展态势。在…

leetcode(力扣) 51. N 皇后 (回溯,纸老虎题)

文章目录 题目描述思路分析对于问题1对于问题2 完整代码 题目描述 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数…

业务出海之服务器探秘

这几年随着国内互联网市场的逐渐饱和&#xff0c;越来越多的公司加入到出海的行列&#xff0c;很多领域都取得了很不错的成就。虽然出海可以获得更加广阔的市场&#xff0c;但也需要面对很多之前在国内可能没有重视的一些问题。集中在海外服务器的选择维度上就有很大的变化。例…

rocksdb中测试工具Benchmark.sh用法(基准、性能测试)

1.首先要安装db_bench工具&#xff0c;这个工具在成功安装rocksdb之后就自动存在了&#xff0c;主要是在使用make命令之后就成功安装了&#xff0c;详情请见我之前的文章 2.确保成功安装db_bench之后&#xff0c;找到安装的rocksdb目录下面的tools文件夹&#xff0c;查看里面是…

怎么改变容易紧张的性格?

容易紧张的性格是比较通俗的说法&#xff0c;在艾森克人格测试中&#xff0c;容易紧张的性格就属于神经症人格&#xff0c;神经质不是神-经-病&#xff0c;而是一种人格特征&#xff0c;这种特征包括&#xff1a;敏感&#xff0c;情绪不稳定&#xff0c;易焦虑和紧张。有兴趣的…

(一)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB

一、七种算法&#xff08;DBO、LO、SWO、COA、LSO、KOA、GRO&#xff09;简介 1、蜣螂优化算法DBO 蜣螂优化算法&#xff08;Dung beetle optimizer&#xff0c;DBO&#xff09;由Jiankai Xue和Bo Shen于2022年提出&#xff0c;该算法主要受蜣螂的滚球、跳舞、觅食、偷窃和繁…

SpringBoot 监控

概述 SpringBoot自带监控功能Actuator&#xff0c;可以帮助实现对程序内部运行情况监控&#xff0c;比如监控状况、Bean加载情况、配置属性、日志信息等。 使用步骤 导入依赖坐标 <dependency><groupId>org.springframework.boot</groupId><artifactI…

HCIA-经典综合实验(一)

经典综合实验&#xff08;一&#xff09; 实验拓扑配置步骤第一步&#xff1a;配置二层VLAN第二步&#xff1a;配置IP地址第三步&#xff1a;配置DHCP服务第四步&#xff1a;配置路由协议OSPF第五步&#xff1a;配置ACLNATTelnet 配置验证测试PC1能不能telnet登录到R1测试所有P…

Leetcode—765.情侣牵手【困难】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—765.情侣牵手 并查集置换环思路 参考自ylb 实现代码 class Solution { public:int minSwapsCouples(vector<int>& row) {int n row.size();int len n / 2;vector<int> p(len);iota(p.begin(), p.…

postman连接数据库

参考&#xff1a;https://blog.csdn.net/qq_45572452/article/details/126620210 1、安装node.js 2、配置环境变量 3、安装xmysql连接数据库cmd窗口输入"npm install -g xmysql"后回车cmd窗口输入"xmysql"后回车,验证xmysql是否安装成功(下图代表安装成功)…

算法通关村第八关-白银挑战二叉树的深度和高度问题

大家好我是苏麟 , 今天说说几道二叉树深度和高度相关的题目 . LeetCode给我们造了一堆的题目&#xff0c;研究一下104、110和111三个题&#xff0c;这三个颗看起来挺像的&#xff0c;都是关于深度、高度的。 最大深度问题 描述 : 二叉树的 最大深度 是指从根节点到最远叶子…