当前位置: 首页 > article >正文

C/C++语言基础--C++检测内存泄露方法、RALL思想模型

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • C++是面向对象的语言,但是更像是面向内存的语言,内存问题一直是C++程序员一直要注意的问题,本篇文章简单的介绍了一下检测内存泄露方法、RALL思想
  • 本篇文章也是为了后面更新指针指针做铺垫
  • C语言后面也会继续更新知识点,如内联汇编;
  • 欢迎收藏 + 关注,本人将会持续更新。

文章目录

  • VS中内存泄漏检测方法
      • atexit 函数
  • RAII
    • 什么是RAII
    • 资源管理问题
    • 案例展示
    • 如何使用RAII
      • 总结

VS中内存泄漏检测方法

首先来看一下内存泄漏的案例,以及如何检测内存泄漏。

1、实质:
🍸概念: 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费;

解释:这句话的以上,看一个程序就明白了:

void test()
{
    int* a = (int*)malloc(sizeof(int));    // 申请了一块内存
    *a = 10;      // 赋值
}
  • 假设:在test作用域中没有释放申请的内存,这个时候我们在后面就无法操作这个申请内存的空间了,但是由于这块内存一直在,没有释放,他就会一直占有这块内存空间,这样就导致了内存泄露。

2、原理:
内存泄露的关键就是跟踪每一块内存的生命周期,记录分配的内存和释放内存的操作,看看能不能匹配,

3、方法:不同开发环境有不同的检测方法,在VS中使用时,需加上

#define _CRTDBG_MAP_ALLOC
#include<crtdbg.h>

crtdbg.h的作用是将mallocfree函数映射到它们的调试版本_malloc_dbg_free_dbg,这两个函数将跟踪内存分配和释放。

_CrtDumpMemoryLeaks();      //内存检测函数

注意:要点击调试才能看出

⚓️ 案例如下:

//Vs中检测内存泄漏代码,C/C++通用
#ifdef _DEBUG

#ifdef  __cplusplus
#include<iostream>
#define new   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define _CRTDBG_MAP_ALLOC
#include<malloc.h>
#include<crtdbg.h>
#include<stdlib.h>
#endif //  __cplusplus

#else
#include<malloc.h>
#endif
//用于atexit注册,会在程序退出时自动调用
void Exit()
{
	_CrtDumpMemoryLeaks();
}

atexit 函数

  • atexit()用来设置一个程序正常结束前调用的函数

测试代码

int main()
{
	atexit(Exit);
	int* p = new int[5];
	//delete[] p;
	return 0;
}
/* 输出窗口中显示:
Detected memory leaks!    // 内存泄露
Dumping objects ->
{157} normal block at 0x00C5E690, 20 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.
*/

RAII

什么是RAII

RAII (Resource Acquisition Is Initialization),也称为**“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法**。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。

🛰 换句话说RAII就是在创建出一个对象后,保证这个对象在他的有效生命周期中一直存在,一旦离开了他的生命周期,就会释放掉,结合类的构造和析构函数,==可以简要的说:==在构造函数中申请的内存没在析构函数中释放⚜️⚜️⚜️.

🍇 扩展阅读

RAII技术被认为是C++中管理资源的最佳方法,进一步引申,使用RAII技术也可以实现安全、简洁的状态管理,编写出优雅的异常安全的代码,它利用栈对象在离开作用域后自动析构的语言特点,将受限资源的生命周期绑定到该对象上,当对象析构时以达到自动释放资源的目的。

资源管理问题

计算机系统中,什么是资源呢?

  • 小编认为主要归纳为两=三个:算力(CPU、GPU等),存储(文件,磁盘,寄存器,内存等),网络通信(基于socket通信等)。
  • 结合计算机运行,在运用层抽象,就有:堆上分配的内存、文件句柄、线程、数据库连接、网络连接、网络套接字、互斥锁和内存等等,这些也分为系统资源

🔼 资源使用?

  • 资源不可能是取之不尽、用之不竭的,从抽象角度,可以归纳为: 获取资源—>使用资源—>释放资源(虽然第一看看起来可能是“废话”,但是随着学习深入,发现这些“废话”有时候却是很重要的)

在这里插入图片描述

案例展示

  • 我们来看一下这个案例:
#include <iostream>

using namespace std;

const int N = 100;

int main()
{
	int* arr = new int[N];

	/*
	………………………………  干活
	*/

	delete[] arr;  // 释放
	arr = nullptr;

	return 0;
}

👁‍🗨 这个案例是一个很简单的一个例子,申请了一块内存,然后释放,但是如果“干活那段代码”很多,那我们是很容易忘记释放内存的❓❓❓

  • 再来一个例子(官方的案例):
#include <iostream> 
using namespace std; 
 
bool OperationA(); 
bool OperationB(); 
 
int main() 
{ 
    int *testArray = new int [10]; 
 
    // Here, you can use the array 
    if (!OperationA()) 
    { 
        // If the operation A failed, we should delete the memory 
        delete [] testArray; 
        testArray = NULL ; 
        return 0; 
    } 
 
    if (!OperationB()) 
    { 
        // If the operation A failed, we should delete the memory 
        delete [] testArray; 
        testArray = NULL ; 
        return 0; 
    } 
 
    // All the operation succeed, delete the memory 
    delete [] testArray; 
    testArray = NULL ; 
    return 0; 
} 
 
bool OperationA() 
{ 
    /*Do some operation, if the operate succeed, then return true, else return false */ 
    return false ; 
} 
 
bool OperationB() 
{ 
    /*Do some operation, if the operate succeed, then return true, else return false*/
    return true ; 
}

👓 上面的案例是一个比较常用的逻辑架构,在主函数main中,当我们对不同情况做不同处理的时候,根据实际场景去释放 testArray 这个数组这块内存,如果所有实际场景都没有满足,则最后释放,从逻辑的角度上说,这个没有任何问题,但是从代码角度上说,不够简介,比较臃肿,这个就是可以用我们的RALL思想去优化代码了

如何使用RAII

RALL核心思想:在使用的时候申请资源,并且保证在他的有效作用域中资源不被释放,但是一旦离开了这个作用域,资源就会立刻释放。

🏗 结合C++语法,使用RALL思想,主要可以靠这两个地方实现:

  • 当我们在一个函数内部使用局部变量,利用栈的特性实现。
  • 当这个变量是类对象时,利用构造和析构函数特性实现。

来个最简单的例子,上面按个案例优化

#include <iostream>

using namespace std;

template <class T>
class Array
{
public:
	// 构造函数中申请内存
	Array(T n) 
		:m_n(n)
	{
		m_data = new T[n];
	}

	// 析构函数中释放内存
	~Array()
	{
		delete[] m_data;
		cout << __FUNCTION__ << endl;
	}


private:
	int m_n;
	T* m_data;
};

int main()
{
	Array<int> array(10);

	return 0;
}

🃏 结果

在这里插入图片描述

  • 这里本人找了一个高端一点的例子:
#include <iostream>
#include <windows.h>
#include <process.h>
 
using namespace std;
 
CRITICAL_SECTION cs;
int gGlobal = 0;
 
class MyLock
{
public:
    MyLock()
    {
        EnterCriticalSection(&cs);
    }
 
    ~MyLock()
    {
        LeaveCriticalSection(&cs);
    }
 
private:
    MyLock( const MyLock &);
    MyLock operator =(const MyLock &);
};
 
void DoComplex(MyLock &lock ) 
{
}
 
unsigned int __stdcall ThreadFun(PVOID pv) 
{
    MyLock lock;
    int *para = (int *) pv;
 
    DoComplex(lock);
 
    for (int i = 0; i < 10; ++i)
    {
        ++gGlobal;
        cout<< "Thread " <<*para<<endl;
        cout<<gGlobal<<endl;
    }
    return 0;
}
 
int main()
{
    InitializeCriticalSection(&cs);
 
    int thread1, thread2;
    thread1 = 1;
    thread2 = 2;
 
    HANDLE handle[2];
    handle[0] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread1, 0, NULL );
    handle[1] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread2, 0, NULL );
    WaitForMultipleObjects(2, handle, TRUE , INFINITE );
    return 0;
}

这个例子可以说是实际项目的一个模型,当多个进程访问临界变量时,为了不出现错误的情况,需要对临界变量进行加锁;上面的例子就是使用的Windows的临界区域实现的加锁。但是,在使用CRITICAL_SECTION时,EnterCriticalSection和LeaveCriticalSection必须成对使用,很多时候,经常会忘了调用LeaveCriticalSection,此时就会发生死锁的现象。当我将对CRITICAL_SECTION的访问封装到MyLock类中时,之后,我只需要定义一个MyLock变量,而不必手动的去显示调用LeaveCriticalSection函数。

总结

​ RAII的本质内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源一定会被释放。


http://www.kler.cn/a/405321.html

相关文章:

  • 【LC】242. 有效的字母异位词
  • 第 24 章 -Golang 性能优化
  • 微信小程序组件详解:text 和 rich-text 组件的基本用法
  • java 并发编程 (1)java中如何实现并发编程
  • 什么是反向 DNS 查找以及它的作用是什么?
  • 算法编程题-排序
  • RTPS通信使用的socket和端口
  • 从零开始:如何使用第三方视频美颜SDK开发实时直播美颜平台
  • 在 Swift 中实现字符串分割问题:以字典中的单词构造句子
  • 摸一下elasticsearch8的AI能力:语义搜索/vector向量搜索案例
  • GPU服务器厂家:为什么要选择 GPU 服务器?
  • 包装器与绑定器
  • 06、Spring AOP
  • Bug Fix 20241122:缺少lib文件错误
  • 低速接口项目之串口Uart开发(四)——UART串口实现FPGA内部AXILITE寄存器的读写控制
  • 历遍单片机下的IIC设备[ESP--0]
  • 浅谈新能源光储充一体化电站设计方案
  • PyTorch图像预处理:计算均值和方差以实现标准化
  • 网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
  • RocketMQ文件刷盘机制深度解析与Java模拟实现
  • Leecode刷题C语言之统计不是特殊数字的数字数量
  • xbh的比赛
  • Qt 的事件投递机制:从基础到实战
  • 动态调试对安全研究有什么帮助?
  • 设计模式之 模板方法模式
  • vue中路由缓存