文章目录
前言
这篇博客主要介绍一些C/C++的内存管理机制,包括C/C++内存分布、申请释放内存方式及底层实现的简单解释、内存泄漏、内存泄漏的检测、如何防止内存泄漏,若有错误之处还请指正。一、C/C++内存分布?
1.内存布局示意图:
2.内存存放的数据类型
栈:非静态局部变量/函数参数/返回值等等,栈是向下增长的。
内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可以使用接口创建共享内存(shm系列接口),用于进程间的通信。
堆:程序运行时动态内存分配的空间,即new/delete/malloc/free,堆是向上增长的。
数据段:全局数据、静态数据。
代码段:可执行代码(二进制),只读常量。
二、C/C++内存管理
1.C内存管理
申请内存:包括malloc、realloc、calloc
//须包括头文件stdlib.h
void * malloc(int n);
//参数解释
n:内存大小,单位字节。
返回值:返回一个void*类型的指针,申请成功返回对应内存地址,申请失败返回NULL;实际使用,需要将返回值强转为我们需要的类型。
//如下:
//申请10个int大小的空间
int * temp=(int*)malloc(sizeof(int)*10);
//realloc、calloc使用机会较少
//realloc :给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度.
void* realloc(void* ptr, unsigned newsize);
// calloc:与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,即在内存中申请numElements*sizeOfElement字节大小的连续地址空间. 只不过calloc会初始化申请的空间
void* calloc(size_t numElements, size_t sizeOfElement);
释放内存:
void free(void* ap)
//ap:既要释放的内存的指针
//无返回值
//释放后最好赋为NULL,防止野指针
free(temp);
temp=NULL;
2.C++内存管理
申请内存:new/new []
//注意:new/delete 为操作符,malloc/realloc/calloc/free为函数。
//申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和
delete[]
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间
int* ptr3 = new int[3];
}
释放内存:delete/delete []
//释放上面申请的空间
delete ptr1;
delete ptr2;
delete[] ptr3;
申请释放自定义类型的空间:
class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test2()
{
//注意:在申请自定义类型的空间时,new会调用构造函数,
//delete会调用析构函数,而malloc与free不会,所以C++中申请自定义类型尽量使用new/delete防止内存泄漏。
// 申请单个Test类型的对象
Test* p1 = new Test;
delete p1;
// 申请10个Test类型的对象
Test* p2 = new Test[10];
delete[] p2; }
operator new与operator delete函数详解
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试
执行空 间不足应对措施,如果该应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) {
// try to allocate size bytes
void *p;
//循环尝试申请,若申请失败,没有内存抛bad_alloc异常,若有内存则一直申请直到成功。
//从这里看出申请内存顺序为 new操作符->operator new->malloc
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
//申请成功直接返回
return (p);
}
operator delete
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData) {
_CrtMemBlockHeader * pHead;
//回调_RTC_Free_hook释放内存
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
//成功返回
if (pUserData == NULL)
return;
//上锁,线程安全
_mlock(_HEAP_LOCK); /* block other threads */
//再次尝试释放
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
//检查该段内存是否被程序占用。一般崩溃是因为如果内存已经释放了,又执行内存释放操作,这里就会报错
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
//调用free 释放
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
//解锁
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return; }
//free的实现
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
类中也可以自己重载operator new和operator delete,提高效率。
三、内存泄漏
1.概念
内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
若一个系统时常运行着存在内存泄露的进程,则随着时间的推移,内存逐渐减少,最终很有可能导致系统奔溃。
内存泄漏包括 系统资源泄漏:打开的系统资源没有关闭,如文件描述符。
堆内存泄漏:即申请的堆内存没有释放。
2.Windows平台下检测内存泄漏
检测是否存在内存泄漏
包含C的以下头文件和宏
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
包含<crtdbg.h>头文件后,它会把 malloc 和 free 函数映射到它们的调试版本,即 _malloc_dbg 和 _free_dbg,_malloc_dbg 和 _free_dbg将跟踪内存的申请和释放,最后在程序退出之前包含以下语句来转储内存泄漏信息。
_CrtDumpMemoryLeaks();
当加上以上所有操作后,调试时_CrtDumpMemoryLeaks()会将内存泄漏信息展示到输出窗口(调试输出窗口)中。
如下,因为new底层也是调用的malloc ,所以也可检测new。
#define _CRTDBG_MAP_ALLOC
#include<iostream>
#include<crtdbg.h>
using namespace std;
int main() {
int* a = new int[10];
_CrtDumpMemoryLeaks();
return 0;
}
输出窗口信息如下:
Detected memory leaks!
Dumping objects ->
{156} normal block at 0x00FA5AA0, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
程序有可能退出的地方有很多,这时我们可以在程序开始运行的地方加上如下语句,它会在程序退出时自动调用_CrtDumpMemoryLeaks()来转储内存泄漏信息。
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
那么问题来了, 如何定位具体内存泄漏的位置了?
要定位到是哪里出现了内存泄漏,必须要有内存的快照。crtdbg头文件为我们定义了结构体_CrtMemState,表示内存的实时状态信息。
通过_CrtMemCheckpoint( _CrtMemState* )可以帮助我们对内存信息进行拍照,并保存到_CrtMemState结构体当中,如下。
//定义结构体
_CrtMemState cs1;
//拍照
_CrtMemCheckpoint( &cs1);
我们已经知道了内存的快照信息,接下来只需要对比不同地方的内存的快照差异,即可定位内存泄漏位置。对比内存快照的差异,通过如下接口。
//如果不同返回true,否则false
int _CrtMemDifference(
_CrtMemState *stateDiff, //保存oldState和newState的差异,入参
const _CrtMemState *oldState,
const _CrtMemState *newState
);
得到快照差异_CrtMemState结构体后,通过如下接口可以随时转储内存泄漏信息。
void _CrtMemDumpStatistics(
const _CrtMemState *state
);
最后,在程序将要结束之前加上_CrtDumpMemoryLeaks()即可
综合示例:
#define _CRTDBG_MAP_ALLOC
#include<iostream>
#include<crtdbg.h>
using namespace std;
int main() {
//定义内存状态结构体
_CrtMemState cs1, cs2,cs3;
_CrtMemCheckpoint(&cs1);
int* a = new int[10];
delete[] a;
int* b = new int[10];
_CrtMemCheckpoint(&cs2);
//比较 CS1和CS2的差异,并将其转储到CS3
if (_CrtMemDifference(&cs3, &cs1,&cs2))
_CrtMemDumpStatistics(&cs3);
_CrtDumpMemoryLeaks();
return 0;
}
通过调试,可以观察到如下输出窗口信息,可知在cs1和cs2之间存在内存泄漏。若无内存泄漏则不会有输出。
0 bytes in 0 Free Blocks.
40 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 0 bytes.
Total allocations: 80 bytes.
Detected memory leaks!
Dumping objects ->
{157} normal block at 0x00AF5AA0, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
注意crtdbg.h只适用于(DEBUG)调试版本的程序
3.Linux平台下检测内存泄漏
Linux下也有类似的检测内存泄漏库:mcheck.h
使用方法很简单,如下:
test.cpp
#include<iostream>
#include<mcheck.h>
#include <stdlib.h>
using namespace std;
int main(){
//使用前要先设置环境变量MALLOC_TRACE
setenv("MALLOC_TRACE","./mtrace_output",1);
//通过mtrace()和muntrace() 定位内存泄漏,
//内存泄漏信息被保存到setenv第二个参数所指定的文件中,
//无需我们自己创建,会自动生成,类型为txt。
mtrace();
int* a=new int[10];
muntrace();
return 0;
}
注意: 生成可执行程序时一定要加上调试选项 -g
g++ -g test.cpp -o test
若存在内存泄漏就会被显示在我们指定的文件中,信息如下;
若不存在start和end之间就无信息。
= Start
@ /home/test/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6:(_Znwm+0x18)[0x7fec16ab0dc8] + 0x188d1c0 0x28
= End
四、C++如何防止内存泄漏
在C中我们只有合理使用malloc和free才能有效防止内存泄露。
但对于C++来说我们可以采用RAII(利用对象的生命周期来管理资源)的思想来防止资源泄露。如:守卫锁、智能指针等。
这里主要介绍下智能指针:
https://blog.csdn.net/weixin_45295598/article/details/109259858
文章审核中