前言
本博客介绍4个动态内存管理相关函数malloc、calloc、realloc、free。这四个函数被包含在头文件<stdlib.h>或<malloc.h>中。
函数介绍
1. malloc
首先看c语言对malloc函数的定义: malloc函数,功能为在堆区中申请一块单位为byte的内存空间,并返回指向此空间的void类型指针。 用例:
int* ptr = (int *)malloc(40);
此代码意为在堆区中申请一块大小为40byte的空间,并通过强制类型转换,返回类型为(int*)的指针ptr。由此我们就得到了堆区上一块能够存储10个int类型数据的内存空间。
如果开辟失败,则malloc会返回空指针NULL。我们可以添加一段判空代码来判断malloc的空间开辟是否成功。
int* ptr = (int *)malloc(40);
if (ptr == NULL)
{
perror("malloc");
return;
}
其中perror("malloc"); 可以帮助我们在程序运行后,如果出现开辟空间失败时,打印一段错误信息,让我们知道是在malloc这个环节出错了。
2. calloc
calloc函数与malloc函数的不同在于,其会将所申请内存的每一个字节初始化为0。
int* ptr = (int *)calloc(40);
if (ptr == NULL)
{
perror("calloc");
return;
}
同样在调用calloc后进行判空。
3.realloc
realloc函数的功能为:调整堆区上申请的空间大小。 如:
int* ptr = (int *)malloc(40);
if (ptr == NULL)
{
perror("malloc");
return;
}
int* tmp = realloc(ptr,80);
if(tmp!=NULL)
{
ptr=tmp;
}
运行此代码段,在realloc增容成功的情况下,我们可以将ptr的大小由原来的40个字节调整为80个字节。 在增容成功后,realloc会返回其调整大小后内存空间的起始地址。 realloc和malloc、calloc一样,如果空间开辟失败,则会返回一个空指针NULL。 需要注意的是,在realloc失败的情况下,如果不进行判空就将realloc的返回值赋给ptr,会使ptr成为空指针,我们也就丢失了之前在ptr所指向空间的存储内容。 因此我们需要创建一个临时变量tmp,对tmp进行判空,如果tmp != NULL 是成立的,说明realloc增容成功。此时令ptr = tmp 是安全的。
realloc在调整内存空间的是存在两种情况: 情况1:原有空间之后有足够大的空间,此时调整好大小后直接返回原有空间的起始地址。当对原有空间进行缩容时,也属于这种情况。 情况2:原有空间之后没有足够大的空间,此时realloc函数会在堆区种重新寻找一块内存空间进行开辟,并将原ptr所指向空间中所存储的数据拷贝至新开辟的空间中。随后,realloc会将ptr所指向的空间free掉,并返回新开辟空间的起始地址。 如下图:
4. free
free函数的作用:释放堆区上所开辟的空间。 如果使用malloc、calloc在堆区上开辟了空间,却在使用完空间后没有释放,这会造成内存泄漏问题。
内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。(摘自维基百科)
free的使用:
int* ptr = (int *)malloc(40);
if (ptr == NULL)
{
perror("malloc");
return;
}
int* tmp = realloc(ptr,80);
if(tmp!=NULL)
{
ptr=tmp;
}
……
free(ptr);
ptr=NULL;
free(ptr)后记得要对ptr进行置空,否则会使ptr成为野指针。因为free只是释放了ptr所指向的内存空间,并没有改变ptr的指向。
动态内存管理中的常见错误
1. 对NULL指针的解引用操作(不进行开辟空间后的判空)
malloc可能存在开辟空间失败的情况。如不对malloc进行判空就解引用,就会出错。
int* ptr = (int*)malloc(INT_MAX);
*ptr = 1;
2. 对动态开辟空间的越界访问
int* ptr = (int*)malloc(40);
if (ptr == NULL)
{
perror("malloc");
return;
}
for(int i = 0;i <= 10;i++)
{
*(ptr + i) = i;
}
包含增容功能的代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int* expand(int** ptr,int* vol)
{
return (int*)realloc(*ptr, sizeof(int) * (*vol + 2));
}
int main()
{
int* ptr = (int*)malloc(40);
int sz = 0;
int vol = 10;
if (ptr == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i <= 10; i++)
{
if (sz == vol)
{
int* tmp=expand(&ptr, &vol);
if (tmp != NULL)
{
ptr = tmp;
}
}
*(ptr + i) = i;
sz++;
}
return 0;
}
3. 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
}
4. 部分释放动态开辟的内存空间
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}
运行后结果如下: 因此,要注意在编写动态开辟内存空间的代码时,最好不要更改起始地址的指向。可以写成*(ptr + i)的形式。
5. 对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}
6. 动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
|