介绍malloc、calloc和realloc
1.malloc函数,其原型如下
void *malloc(size_t size);
malloc所分配的是一块连续的内存,其参数就是需要分配的内存字节数。如果分配成功,则返回指向这块内存的指针;如果分配失败,则返回NULL指针
2.calloc函数,其原型如下
void *calloc(size_t num_elements, size_t size_element);
calloc函数也是分配内存的,其参数包含所需元素的个数和每个元素的字节大小,将对分配好的内存进行初始化为0。
3.realloc函数,其原型如下
void realloc(void *ptr, size_t new_size);
realloc函数用于修改一个原先分配的内存,扩大或缩小内存都是在原先内存块后面进行的,原先数据内容保持不变。如果无法改变原先的内存大小,realloc将申请分配另一块与原先数据内容匹配的内存,并把其内容复制到新的内存块上。因此,在使用realloc之后,就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。
4.free函数,其原型如下:
void free(void *pointer);
free的参数必须是从malloc、calloc和realloc返回的值,以释放动态分配后不再使用的内存,但是不能free两次,采用“一夫一妻制”。如果free的参数是NULL,将不会产生任何效果。
malloc函数的使用解决的问题:
1)内存分配给谁?
2)分配多大内存?
3)是否还有足够的内存分配?
4)内存将用来存储什么格式的数据?
5)分配好的内存在哪里?
使用malloc函数之后需要注意的问题:
1)使用free函数释放动态分配的内存,但是不可以对同一块内存释放两次。
2)释放内存后一定要给指针置空,不然该指针就成为了“野指针”,因为使用free函数后指针变量本身保存的地址并没有改变。
用malloc函数申请0字节内存
申请0字节内存,函数并不会返回NULL,而是返回一个正常的内存地址,但是你却无法使用这块大小为0的内存。这好比尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度有间隔才能量出长度。这时,if(NULL != p)语句校验不起作用。
总结
(1)相同点:
1)malloc和calloc函数都能动态分配一块内存,并返回指向这块内存的指针。
2)当一块动态分配的内存块不再使用时,应该调用free函数释放内存,而且不能再被访问。
3)如果内配分配失败将返回NULL指针(malloc、calloc和realloc都一样)。
4)malloc、calloc和realloc函数的返回值相同,都是返回“void *”。
(2)不同点:
1)malloc分配好的内存不初始化,而calloc分配好的内存将被初始化为0。
2)malloc和calloc请求内存的方式不同。
3)realloc函数修改一块已经动态分配的内存。
注意
1)内存泄露指内存被动态分配后,当不再使用时没有被释放。内存泄露会增加程序的体积,有可能导致程序或系统的崩溃。
2)使用malloc和calloc函数分配内存要检查是否成功的情况。
3)不要越过动态分配的区域访问。
4)动态内存被释放后不能再被访问。
5)是malloc、calloc和realloc分配的内存才能用free释放,并且不能只释放一部分,而是整块的释放。
6)使用sizeof计算数据类型的长度,可以提高程序的可移植性。
代码一:
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
运行结果:
系统崩溃。因为GetMemory并不能传递动态内存,Test函数中的str一直都是NULL,“strcpy(str, “hello world”);”将使程序崩溃
代码2:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
运行结果:
可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是NULL,但其原有的内存已经被清除了,新内容不可知。
代码3:
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
运行结果:
篡改动态内存区的内容,后果难以预测,非常危险。因为free(str)之后,str成为野指针,if (str != NULL)语句不起作用。
内存分配方式
1)从静态存储区分配,如:全局变量、static变量等。
2)在堆栈上分配,向下增长,如:局部变量、形式参数和返回值等
3)从堆或自由存储空间上分配,亦称为动态内存分配,如malloc函数和new运算符申请的内存(使用free和delete释放动态内存)。
常见的内存错误及解决方式
1)错误:内存分配未成功,却使用了它。
对策:在使用内存前检查是否有效。
如果指针p是函数的参数,那么在函数的入口处用assert(p != NULL)进行检查以免输入非法参数。
如果用malloc() 或new来申请内存,应该用if(p == NULL)、if(p != NULL)捕获异常来进行处理。
2)错误:内存分配虽然成功,但是尚未初始化就使用它。
对策:不要忘记初始化指针、数组和动态内存,
防止将未初始化的内存作为右值使用。
3)错误:内存分配成功并初始化了,但是越界使用。
对策:避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作
4)错误:没有释放动态内存或者只释放了部分内存,造成了内存泄露。这种情况在使用动态分配的对象数组时极容易发生,比如:你无意中修改了指向动态数组的指针,或者使用错误的动态数组释放方法等。
对策:动态内存申请和释放必须配对
程序中malloc() 和free() 使用次数必须相同,防止内存泄露(new和delete也一样)。
5)错误:释放了内存却还在继续使用它。有以下几种情况:
1)函数中return语句返回了栈内存的指针或者引用,因为该内存在函数结束时被自动释放。
2)释放了动态内存后,没有将指针置空,产生了野指针。
代码:
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);//str所指向的内存被释放,但是str本身仍然不变
//应加上str = NULL;这条语句
if (str != NULL)//没有起到防错作用
{
strcpy(str, "world");//运行时出现错误
printf(str);
}
}
3)多次释放同一块内存。
4)指针操作超越了变量的作用范围
class A
{
void Func(void)
{
cout << "A::func()" << endl;
}
};
void Test(void)
{ //外层程序块
A *p = NULL;
{ //内层程序块
A a;
p = &a; //a的生命周期在程序块内
}
p->Func(); //p是野指针
}
对策:用free函数和new运算符释放内存后立即将指针置空
有了malloc/free为什么还要new/delete?
1)malloc和free是C++/C语言的标准库函数,new/delete是C++的运算符,它们都可以用于申请内存/释放内存。
2)C++语言需要一个能够完成动态内存分配和初始化工作的运算符new,以及一个能够完成清理与释放内存工作的运算符delete。并且,使用new/delete更加安全。
3)malloc/free需要手动计算类型大小且返回值为void*,new/delete可以自己计算类型的大小,返回对应类型的指针。
new/delete的功能完全覆盖了malloc() 和free(),为什么C++还用malloc()和free()?
1)C++程序会经常调用C程序,而C程序只能用malloc()/free()来管理动态内存。
2)可以为自定义类重载new/delete,而malloc()和free()不能被任何类重载。
3)在某些情况,malloc/free比new/delete效率高。
通过new和delete管理动态内存。
new/delete管理动态内存和new[]/delete[]管理动态内存。
代码:
void Test2()
{
int *p4 = new int;//动态分配4个字节(1个int)的空间单个数据
int *p5 = new int(3);//动态分配4个字节(1个int)的空间单个数据,并初始化为3
int *p6 = new int[3];//动态分配12个字节(3个int)的空间
int *p7 = (int*)malloc(sizeof(int));
delete p4;
delete p5;
delete []p6;
free(p7);
}
它们的操作过程如下图:
new的三种使用方式
1)plain new/delete
void *operator new(std::size_t)throw(std::bad_alloc);
void operator delete(void *)throw();
//plain new在失败后抛出标准异常std::bad_alloc而不是返回NULL。
代码:
char *Getmemory(unsigned long size)
{
char *p = new char[size];
return p;
}
void main()
{
try
{
char *p = Getmemory(1000000); //可能抛出std::bad_alloc异常
//...
delete[]p;
}
catch (const std::bad_alloc&ex)//??????
{
cout << ex.what() << endl;
}
}
2)nothrow new/delete
void *operator new(std::size_t, const std::nothrow_t&)throw();
void operator delete(void *)throw();
Nothrow new就是不抛出异常的运算符new的形式,nothrow new在失败时返回NULL。Nothrow是C++语言实现定义的一个nothrow_t 的全局const对象
代码:
void func(unsigned long length)
{
unsigned char *p = new(nothrow)unsigned char[length];
if (p == NULL)
{
cout << "allocte failed!" << endl;
}
//...
delete[]p;
}
3)placement new/delete
Placement意为“放置”,这种new形式允许在一块已经分配成功的内存上重新构造对象或者对象数组,所以它不用担心内存分配失败。
注:
1)placement new的主要用途就是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。
先申请一个足够大的字符数组内存,然后当需要时在它上面构造不同类型的对象或数组,代码:
#include<iostream>
#include<new>
using namespace std;
void main(){
char *p = new(nothrow)char[100];
if (p == NULL)
{
cout << "allocte failed!" << endl;
exit(-1);
}
//...
long *q1 = new(p) long(100);//placement new不担心失败
//...
int *q2 = new(p)int[100 / sizeof(int)];//构造不同的数组
//...
ADT *q3 = new(p)ADT[100 / sizeof(ADT)];
//...
delete[]p;//释放内存
}
2)placement new构造起来的对象或者其数组的大小并不一定等于原来分配的内存大小,因此使用delete会造成内存泄露,或者在之后释放内存时会出现运行错误,应该显示地调用它们的析构函数来销毁(析构函数并不释放对象的内存)
#include<iostream>
#include<new>
using namespace std;
void main()
{
char *p = new(nothrow)char[sizeof(ADT) + 2];
if (p == NULL)
{
cout << "allocte failed!" << endl;
exit(-1);
}
ADT *q = new(p)ADT;
//...
//delete q; //错误!不能再此处释放内存
q->ADT::~ADT(); //显示调用析构函数
delete[]p; //再释放内存
}