malloc/calloc/realloc的区别?
1. malloc
函数原型:
void *malloc(size_t size);
函数功能:
malloc()在内存的动态存储区中分配一块长度为size字节的连续区域。参数size为需要的内存空间的长度,返回该区域的地址。
区别:
malloc不能初始化所分配的内存空间,需要用memset,而函数calloc能初始化。如果这部分内存曾经被分配过,则其中可能遗留各种各样的数据。
2. calloc
函数原型:
void *calloc(size_t nmemb, size_t size);
函数功能:
calloc()与malloc()相似,参数size为申请地址的单位元素长度,nmemb为参数个数。
区别:
calloc会将所分配的空间中的每一位都初始化为零。
3. realloc
函数原型:
void *realloc(void *ptr, size_t size);
函数功能:
realloc()是给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址空间。
区别:
realloc可以对给定的指针所指向的空间进行扩大或缩小,原有的内存中的内容将保持不变。realloc并不保持调整后的内存空间和原来的内存空间保持同一内存地址,返回的指针很可能指向新的地址。
内存泄漏?如何检测内存泄漏?
C++中的内存泄露一般指堆中的内存泄露。堆内存是我们手动malloc/realloc/new申请的,程序不会自动回收,需要调用free或delete手动释放,否则就会造成内存泄露。
内存泄露的关键就是记录分配的内存和释放内存的操作,看看能不能匹配。跟踪每一块内存的生命周期。
例如:每当申请一块内存后,把指向它的指针加入到List中,当释放时,再把对应的指针从List中删除,到程序最后检查List就可以知道有没有内存泄露了。Window平台下的Visual Studio调试器和C运行时(CRT)就是用这个原理来检测内存泄露。
在VS中使用时,需加上
crtdbg.h的作用是将malloc和free函数映射到它们的调试版本_malloc_dbg和_free_dbg,这两个函数将跟踪内存分配和释放(在Debug版本中有效)。
_CrtDumpMemoryLeaks函数将显示当前内存泄露,也就是说程序运行到此行代码时的内存泄露,所有未销毁的对象都会报出内存泄露,因此要让这个函数尽量放到最后。
举例如下:
上述代码中,我们只释放了一块内存,运行调试,会在output窗口输出:
可以看到检测到了内存泄露。
但是并没有检测到泄露内存申请的位置,我们已经加了宏定义#define _CRTDBG_MAP_ALLOC。原因是申请内存用的是new,而刚刚包含头文件和加宏定义是重载了malloc函数,并没有重载new操作符,所以要自己定义重载new操作符才能检测到泄露内存的申请位置。修改如下:
我们再看调试结果:
源.cpp()括号里面的数字就是泄露内存的起始位置。
后面的{144} normal block at 0x0361B1C0, 50 bytes long代表什么呢?
大括号{}里面的数字表示第几次申请内存操作;0x0361B1C0表示泄露内存的起始地址,CD CD表示泄露内存的内容。
调用long _CrtSetBreakAlloc(long nAllocID)可以再第nAllocID次申请内存时中断,在中断时获取的信息比在程序终止时获取的信息要多,你可以调试,查看变量状态,对函数调用调试分析,解决内存泄露。
block分为3中类型,此处为normal,表示普通,此外还有client表示客户端(专门用于MFC),CRT表示运行时(有CRT库来管理,一般不会泄露),free表示已经释放掉的块,igore表示要忽略的块。
在上面程序中,调用_CrtDumpMemoryLeaks()来检测内存泄露,如果程序可能在多个地方终止,必须在多个地方调用这个函数,这样比较麻烦,可以在程序起始位置调用_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF| _CRTDBG_LEAK_CHECK_DF),这样无论程序何时终止,都会在终止前调用_CrtDumpMemoryLeaks()。
除此之外,还可以在某时刻设置检查点,获取当时内存状态的快照。比较不同时刻内存状态的差异。
调试结果:
new/delete操作符
我们都知道,new/delete是对于单个对象空间,new[]/delete[]是对于连续空间,且必须配对使用。
先看几个C++语言标准库的库函数:
看一个例子:
上面类A中有两个私有成员,有一个构造函数和一个析构函数,构造函数中初始化私有变量 var 以及打开一个文件,析构函数关闭打开的文件。
我们使用class A *pA = new A(10);来创建一个类的对象,返回其指针 pA。
new 背后完成的工作:
- 首先需要调用上面提到的 operator new 标准库函数,传入的参数为 class A 的大小,这里为 8 个字节。
- 上面分配的内存是未初始化的,也是未类型化的,第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数,这里是调用 A::A(10); 这个函数,var=10, file 指向打开的文件。
- 最后一步就是返回新分配并构造好的对象的指针,这里 pA 就指向这块内存,pA 的类型为类 A 对象的指针。
那么 delete 都干了什么呢?还是接着上面的例子,如果这时想释放掉申请的类的对象怎么办?我们使用下面的语句来完成:
delete pA;
delete 所做的事情:
- 调用 pA 指向对象的析构函数,对打开的文件进行关闭。
- 通过上面提到的标准库函数 operator delete 来释放该对象的内存,传入函数的参数为 pA 的值。
如何申请和释放一个数组?
我们经常要申请一个数组,如下:
上面在申请一个数组时都用到了new[]这个表达式来完成:
第一个数组是 string 类型,分配了保存对象的内存空间之后,将调用 string 类型的默认构造函数依次初始化数组中每个元素;
第二个是申请具有内置类型的数组,分配了存储 10 个 int 对象的内存空间,但并没有初始化。
如果我们想释放空间了,可以用下面两条语句:
都用到 delete[]表达式,注意这地方的 [] 一般情况下不能漏掉。
我们也可以想象这两个语句分别干了什么:第一个对 10 个 string 对象分别调用析构函数,然后再释放掉为对象分配的所有内存空间;
第二个因为是内置类型不存在析构函数,直接释放为 10 个 int 型分配的所有内存空间。
我们如何知道 psa 指向对象的数组的大小?怎么知道调用几次析构函数?
C++ 的做法是在分配数组空间时多分配了4个字节的大小,专门保存数组的大小,然后才是对象数组指针的地址,在delete[]时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。
我们需要注意:
- 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
- 传入 operator delete[] 函数的参数不是数组对象的指针p,而是 p 的值减 4。
malloc/free&&new/delete的区别
- malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
- new能够自动分配空间大小
- 对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++需要一个能对对象完成动态内存分配和初始化工作的运算符new,以及一个能对对象完成清理与释放内存工作的运算符delete