C语言动态内存管理
申请内存的方式
- 栈上(静态内存管理)
1.生命周期随栈帧。栈帧结束,内存归还给操作系统,无需程序猿管理。
2.空间开辟的大小固定,申请数组时数组的大小必须指定。他内存在编译时期就已经分配好。
3.程序在运行时期不能进行内存申请。而这种情况在编程中又很常见。所以就需要动态内存管理。
- 堆上(动态内存管理)
1.生命周期由程序猿管理。需要手动申请手动释放。且必须释放,否则就会造成内存泄露
2.开辟空间大小由程序猿指定,更灵活。空间分配在程序运行期间分配。
对比:栈上申请内存比较省心,规规矩矩。缺乏灵活性,而堆上申请内存更为灵活。但是灵活也就意味着风险。如果只申请,不释放,就会造成内存泄露。释放的内存的申请的内存要匹配。
动态内存管理函数介绍
malloc,calloc,realloc负责申请内存free负责释放内存(取消指针与内存对应的关系。
函数原型:
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
malloc解析:
- 该函数向内存申请一块连续可用的空间,并返回指向这块内存的指针。
- 如果开辟空间成功,则返回一个开辟好空间的指针
- 如果申请失败,则返回一个NULL指针,因此malloc的返回值一定要进行检查,如果返回NULL,在进行解引用,程序就会崩溃。
- 返回值的类型是void*,所以malloc开辟的空间并不知道里面应该存储什么类型的数据。具体使用时,就必须进行强转。
- 参数size由程序猿指定,如果size=0,则malloc的行为则是未定义的。取决于编译器
- malloc申请的空间必须释放,且只能释放一次,释放的空间和申请的空间需要一一对应。
free()解析:
- free用来释放动态开辟的内存,所谓的释放就是取消指针与内存的对应关系。
- 参数如果ptr指向的空间不是动态申请的。则free的结果就是未定义的。
- 如果ptr是NULL,则free什么也不做。
正确使用:
#include<stdio.h>
#include <stdlib.h>
int main()
{
int num=10;
int *p=NULL;
p=(int*)malloc(num*sizeof(int));
if(p!=NULL)
{
int i=0;
for(;i<num;i++)
{
*(p+i)=0;
printf("%3d",*p);
}
}
free(p);
return 0;
}
如释放两次:
就会报错:
[zyc@localhost malloc]$ gcc -g malloc.c
[zyc@localhost malloc]$ ./a.out
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000970010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3aaa276166]
./a.out[0x4005e1]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x3aaa21ed1d]
./a.out[0x400499]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:02 412407 /home/zyc/c/malloc/a.out
00600000-00601000 rw-p 00000000 08:02 412407 /home/zyc/c/malloc/a.out
00970000-00991000 rw-p 00000000 00:00 0 [heap]
3aa9a00000-3aa9a20000 r-xp 00000000 08:02 782608 /lib64/ld-2.12.so
3aa9c1f000-3aa9c20000 r--p 0001f000 08:02 782608 /lib64/ld-2.12.so
3aa9c20000-3aa9c21000 rw-p 00020000 08:02 782608 /lib64/ld-2.12.so
3aa9c21000-3aa9c22000 rw-p 00000000 00:00 0
3aaa200000-3aaa38b000 r-xp 00000000 08:02 782609 /lib64/libc-2.12.so
3aaa38b000-3aaa58a000 ---p 0018b000 08:02 782609 /lib64/libc-2.12.so
calloc函数解析:
- 该函数也用来进行动态内存分配。不过该函数是给num个大小为size的元素开辟一块连续的存储空间。并把空间的每个字节都初始化为0.
- 既然是动态开辟的空间,就需要动态释放free掉。
realloc函数解析:
void *realloc(void *ptr, size_t size);
- reallloc函数让动态内存分配更加灵活。可动态扩容。并自动将原来的数据搬运到新空间上。
- 参数ptr是要调整的内存地址,size是调整之后的内存大小。
- 返回值是调整之后的新的内存地址(不一定)
- 在调整空间上存在两种情况:
情况一:原来的空间之后有足够大小,要扩展内存就直接在后面追加即可。原来空间的数据不发生改变
情况二:原来的空间之后没有足够大小。要扩容的话就必须在堆上另外找一个合适大小的连续空间来使用。这时返回的就是新地址,且数据发生了搬迁。
常见动态内存错误:
- 对NULL进行解引用
- 对动态开辟的内存进行越界访问
- 对非动态开辟的内存进行free
- free动态内存的一部分,而不是全部释放
- 对同一块内存释放多次次
- 忘记free,造成内存泄露。这个问题很严重。
C++动态内存管理
C++中使用new和delete两个关键字动态管理内存
new和delete 动态管理对象
new[]和delete[] 动态管理对象数组
void test()
{
int *p1=new int;//动态分配四个字节(一个int)的空间
int *p2=new int(3);//动态分配四个字节(一个int)的空间并初始化为4
int *p[]=new int[3];//动态分配12字节(3个int)的空间
delete p1;
delete p2;
delete p3[];//匹配释放
}
解析内存管理
静态全局数据区:存放全局数据和静态数据
代码段:存放可执行代码和只读常量
堆区:存放动态开辟的变量
栈区:非静态局部变量、函数参数,返回值等
C++内存管理的其他函数
void * operator new(size_t size)
void * operator delete(size_t size)
void * operator new[](size_t size)
void * operator delete[](size_t size)
1.这些函数不是new和delete的函数重载
2.用法和malloc函数一样。他们只是分配空间,释放空间。不会调用构造和析构函数。实际上就是malloc的一层封装。
3.实际是为new 和delete准备的。负责调用malloc申请空间和抛异常
new->operator new=malloc+抛异常(bad_alloc)
delete->operator delete
new做了两件事:(new[N]和delete[N]同理)
调用operator new ,然后 operator new再调用malloc申请空间。
调用构造函数初始化
Array *p1=new Array;->call operator new()->call malloc()
delete 做了两件事
调用析构函数清理对象
调用operator delete释放空间
new/delete调用构造函数过程解析
class Array
{
public:
Array(size_t size=10)
:_size(size)
,_a(0)
{
cout<<"Array(size_t size)"<<endl;
if(size>0)
{
_a=new int[size];
}
}
~Array()
{
cout<<"~Array()"<<endl;
if(_a)
{
delete[]_a;
_a=0;
_size=0;
}
}
private:
int * _a;
size_t _size;
};
int main()
{
Array* p1=new Array;
delete p1;
Array* p2=new Array[5];
delete []p2;
return 0;
}
结果:
[zyc@localhost malloc]$ ./a.out
Array(size_t size)
~Array()
Array(size_t size)
Array(size_t size)
Array(size_t size)
Array(size_t size)
Array(size_t size)
~Array()
~Array()
~Array()
~Array()
~Array()
再定义一个malloc申请的变量:
int main()
{
Array* p1=new Array;
delete p1;
Array* p2=new Array[5];
delete []p2;
//定义malloc申请的变量
Array* p3=(Array*)malloc(sizeof(Array));
free(p3);
//定义内置类型
int *p4=new int[];
delete[] p4;
return 0;
}
运行结果:
[zyc@localhost malloc]$ ./a.out
Array(size_t size)
~Array()
Array(size_t size)
Array(size_t size)
Array(size_t size)
Array(size_t size)
Array(size_t size)
~Array()
~Array()
~Array()
~Array()
~Array()
结果还是和上面一样。所以总结如下
自定义变量时,new和delete会自动调用默认成员函数。new 时调用构造函数,delete时调用析构函数。
自定义类型中,new [N] 就会调用N次构造函数,delete就会调N次析构函数。其中N是自定义多开四字节内存,专门用来保存析构的次数。所以在动态管理数组时,自定义內型往往要比内置类型开的空间多四字节。
实际上new 的工作过程。
补充:
为什么new和delete要匹配释放?
下图是new 和new[N]的内存布局图
因为new 和new[]对比,new[N]会在空间的头上多开四字节空间,用来保存对象的个数,该对象个数用来指示析构的时候,要析构多少次。
如果new []和new都用delete释放,要么new【】的指针就会从头部开始释放而头部四字节空间不属于动态管理的空间,你去释放了就会导致释放了不属于你管辖空间,程序就会报错。
new/delete和malloc/free区别和联系
联系:
- 都是动态内存管理的 入口。
- new的本质还是调用了malloc函数。
区别:
- new和delete是关键字,malloc/free是库函数
- new操作符在自由存储区分配空间,不一定是堆上,还有可能是静态存储区,具体是哪里,取决于operator new的实现细节。new甚至可以不为对象分配空间,但malloc申请必须是堆上
- 对数组进行处理时,C++中new[]/delete专门来处理,而mlloc都一视同仁。
- new可以调用malloc,但malloc不可以调用new
- malloc/free只是动态分配空间,释放空间。而new和delete除了分配空间,还会调用构造函数和析构函数进行初始化和清理。
- malloc/free需要手动计算类型大小并返回void*,new/delete自动计算大小,返回对应指针。
malloc申请失败会返回空指针,而new/delete失败会抛异常(bad_alloc),这里不用if/else判断,需要用try/catch捕捉异常
malloc可以直观的重新分配内存,当内存不够时 ,可以调用realloc来重新申请空间。将数据拷贝过去并释放原来的空间,而new 没有这样的机制。