椋鸟C++笔记#5:C++内存管理

2024-06-11 07:36
文章标签 c++ 内存 笔记 管理 椋鸟

本文主要是介绍椋鸟C++笔记#5:C++内存管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • C语言中的动态内存管理
      • C\+\+中的动态内存管理
        • 使用new/delete操作内置类型
        • 使用new/delete操作自定义类型
        • operator new(operator new[])与operator delete(operator delete[])函数
          • operator new函数
          • operator delete函数
          • operator new[]和operator delete[]
        • new与delete的实现原理
        • 定位new表达式(placement-new)
      • 检测内存泄露

萌新的学习笔记,写错了恳请斧正。

C语言中的动态内存管理

C语言中,我们使用malloccallocreallocfree来动态管理内存。

int main()
{int* p = (int*)malloc(sizeof(int));	//动态开辟free(p);int* q = (int*)calloc(4, sizeof(int));	//动态开辟并赋值int* r = (int*)realloc(q, 10 * sizeof(int));	//重新分配空间free(r);
}

C++中的动态内存管理

C++中有更简单方便的内存管理方式,那就是使用newdelete来管理内存。

使用new/delete操作内置类型

注意:如果使用new开辟空间就用delete删除,如果使用new[]开辟数组就用delete[]删除。

  1. 动态申请与删除一个内置类型变量的空间:

    int* pi = new int;
    int* pf = new float;
    int* pl = new long;
    int* pb = new bool;delete pi;
    delete pf;
    delete pl;
    delete pb;
    
  2. 动态申请与删除一个内置类型变量的空间并完成初始化:

    int* pi = new int(114514);
    int* pf = new float(3.14f);
    int* pl = new long(1145141919810);
    int* pb = new bool(true);delete pi;
    delete pf;
    delete pl;
    delete pb;
    
  3. 动态申请与删除内置类型变量数组的空间:

    int* pi = new int[5];
    int* pf = new float[5];
    int* pl = new long[5];
    int* pb = new bool[5];delete[] pi;
    delete[] pf;
    delete[] pl;
    delete[] pb;
    
  4. 动态申请与删除内置类型变量数组的空间并完成初始化:

    int* pi = new int[5]{1, 2, 3, 4, 5};
    float* pf = new float[5]{1.1f, 2.2f, 3.3f, 4.4f, 5.5f};
    long* pl = new long[5]{10, 20, 30, 40, 50};
    bool* pb = new bool[5]{true, false, true, false, true};delete[] pi;
    delete[] pf;
    delete[] pl;
    delete[] pb;
    
使用new/delete操作自定义类型

这与自定义类型区别主要在于:使用new/delete操作自定义类型时会自动调用自定义类型的构造/析构函数。

class A
{
public:A(int a = 0): m_a(a){cout << "A" << endl;}~A(){cout << "~A" << endl;}private:int m_a;
};int main()
{A* oA = new A;delete oA;A* oB = (A*)malloc(sizeof(A));free(oB);return 0;
}

该程序运行结果为:

A
~A
operator new(operator new[])与operator delete(operator delete[])函数

看到operator new和operator delete,很多人会以为这是new与delete的重载函数。

但是,operator new和operator delete不能理解为是new与delete的重载函数!

operator new与operator delete其实是系统提供的全局函数,而new和delete在底层其实就是调用了这两个函数来实现的。

以下给出的函数定义随编译器不同有所变化!

operator new函数
void* operator new(std::size_t size) 
{if (size == 0)	// 如果请求的大小为0,分配至少一个字节{size = 1;}while (true){void* p = std::malloc(size);  // 使用malloc分配内存if (p) {return p;  // 如果分配成功,返回指针}std::new_handler handler = std::get_new_handler();  // 获取当前的new_handlerif (!handler) {throw std::bad_alloc();  // 如果没有设置new_handler,抛出bad_alloc异常}handler();  // 调用new_handler}
}
operator delete函数
void operator delete(void* ptr) noexcept	//noexcept说明此函数不会抛出异常
{std::free(ptr);  // 使用free释放内存
}
operator new[]和operator delete[]

同样的,new[]和delete[]在底层调用的是operator new[]和operator delete[]函数。

void* operator new[](std::size_t size) 
{if (size == 0) {size = 1;}while (true) {void* p = std::malloc(size);if (p) {return p;}std::new_handler handler = std::get_new_handler();if (!handler) {throw std::bad_alloc();}handler();}
}void operator delete[](void* ptr) noexcept 
{std::free(ptr);
}
new与delete的实现原理

如果申请的是内置类型的空间,new和malloc、delete和free基本类似。

不同的是:

  1. new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间
  2. new在申请空间失败时会抛异常;malloc会返回NULL。

对于自定义类型:

  1. new的原理:
    • 调用 operator new 函数申请空间。
    • 执行构造函数。
  2. delete的原理:
    • 执行析构函数。
    • 调用 operator delete 函数释放空间。
  3. new[]的原理:
    • 调用 operator new[] 函数申请空间。
    • 分别执行构造函数。
    • 在这片空间前紧挨的位置申请4个字节用于保存N。
  4. delete[]的原理:
    • 往前读取4个字节,获得N。
    • N个元素分别执行析构函数。
    • 调用 operator delete[] 函数释放空间。
定位new表达式(placement-new)

我们可以直接通过指针显式的调用析构函数:

#include <iostream>using namespace std;class A
{
public:A(int a = 0): m_a(a){cout << "A()" << this << endl;}~A(){cout << "~A()" << this << endl;}private:int m_a;
};int main()
{A* p1 = new A;p1->~A();free(p1);return 0;
}

那我们能直接显式调用构造函数吗?答案是否定的。

但是我们可以使用定位new表达式

#include <iostream>using namespace std;class A
{
public:A(int a = 0): m_a(a){cout << "A()" << this << endl;}~A(){cout << "~A()" << this << endl;}private:int m_a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));new (p1) A;	//在地址p1处执行类A的构造函数p1->~A();free(p1);return 0;
}

其结构就是new加上(这里放地址)加上类名

或者也可以同时初始化:

int main()
{A* p1 = (A*)malloc(sizeof(A));new (p1) A (10);	//在地址p1处执行类A的构造函数并初始化为10p1->~A();free(p1);return 0;
}

当然我们也能显示的调用operator new(operator new[])与operator delete(operator delete[])函数:

#include <iostream>using namespace std;class A
{
public:A(int a = 0): m_a(a){cout << "A()" << this << endl;}~A(){cout << "~A()" << this << endl;}private:int m_a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));new (p1) A;p1->~A();free(p1);A* p2 = (A*)operator new(sizeof(A));new (p2) A(10);p2->~A();operator delete(p2);return 0;
}

检测内存泄露

在 Visual Stodio 中我们可以使用_CrtDumpMemoryLeaks()函数来检测内存泄露,如果发生内存泄露这个函数会在输出窗口给出提示。

#include <iostream>using namespace std;int main()
{int* p = new int;_CrtDumpMemoryLeaks();return 0;
}

执行这段代码,我们可以在来源为调试的输出看到如下内容:

Detected memory leaks!
Dumping objects ->
{81} normal block at 0x00000221287C7400, 4 bytes long.Data: <    > CD CD CD CD 
Object dump complete.

这篇关于椋鸟C++笔记#5:C++内存管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1050539

相关文章

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性: