目录
一、前言
二、malloc和free
1、malloc()函数
2、free()函数
三、calloc
四、realloc函数
五、常见的动态内存错误
一、前言
为什么需要动态内存分配?
当我们创建数组时,如:
int val=20;
int arr[20]={0};
数组开辟的内存是固定的,而实际过程中,有时候我们是无法预知程序所需要的空间大小,内存要是开辟大了,浪费空间,开辟小了,程序出错。这就需要一种方法来灵活控制开辟空间的大小,在这种时候,我们就需要用到动态内存。
二、malloc和free
1、malloc()函数
malloc是c语言用来开辟动态内存的函数,这个函数向内存申请了一块连续可用的空间,并返回指向这块空间的起始地址。
void* malloc(size_t size);
1、如果开辟成功,则返回一个指向开辟好空间的指针。
2、如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3、返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定;参数size的类型是无符号整型,表示需要申请多少个字节的空间。
使用示例:
//申请40个字节的空间 int* p = (int*)malloc(40);
2、free()函数
free()函数是搭配malloc()函数使用的,用来释放malloc申请的内存空间。如果不对malloc函数申请的空间进行释放,那么这块空间只有到整个程序结束时才释放,如果这个程序永远不会停止,那么这块空间就无法被其他程序使用,造成内存泄漏问题(即空间浪费问题)。
void free(void* ptr)
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr是NULL指针,则函数什么事都不做。
参数ptr是指向的动态内存的指针
下面给出一个malloc与free的完整例子:
int main()
{
//int arr[10] = {0};
//申请空间
int* p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
//开辟成功了
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//释放空间
free(p);
p = NULL;
return 0;
}
三、calloc
calloc函数也是用来申请动态内存的,原型如下:
void* calloc(size_t num,size_t size);
1、函数的功能是开辟num个元素,大小为size个字节的空间,并且把空间的每个字节初始化为0。
2、这个函数与malloc大体一致,唯一的区别就是会将每个字节初始化为0.
下面给出一个例子:
int main()
{
//申请10个int的空间
int*p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%sn", strerror(errno));//这句代码的作用是打印错误信息。
return -1;
}
//申请成功
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//空间不够了,增加空间至20 个int
int*ptr = (int*)realloc(p, 20*sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
else
{
//申请失败
return -1;
}
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
//打印
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
四、realloc函数
当我们申请了一块动态内存,使用的过程中,发现申请的这块内存太大或者太小了,这个时候,我们就需要运用realloc函数来调整这块空间的大小,这个函数的原型如下:
void* realloc (void* ptr, size_t size);
ptr是要调整的内存地址.
size调整之后新大小(单位为字节)
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。realloc在调整内存空间的是存在两种情况∶
情况1︰原有空间的后面有足够大的空间,能够满足size个字节,就直接在旧空间的后面开辟。例如,我原本开辟了20个字节的空间,现在我需要40个字节的空间,如果旧空间的后面有20个字节的空间,那这个函数就直接在后面开辟20个空间,这样在加上原本的20个字节空间的地址,刚好满足40个空间,然后返回这块空间的起始地址,如果开辟失败,返回NULL。
情况2:原有空间的后面没有足够大的空间,不能够满足size个字节,那么就得新找一块size个空间来开辟。例如,我原本开辟了20个字节的空间,现在我需要40个字节的空间,如果旧空间的后面只有10个字节的空间,这样我就得重新找一块40个字节空间来开辟,其中原本的旧空间的20个地址会被释放,且这20个空间的内容会被复制到新空间的前20个字节上,最后返回这块新空间的起始地址,如果开辟失败,返回NULL。
代码示例:
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
//开辟成功,业务处理
}
else
{
//开辟失败,退出程序
exit(EXIT_FAILURE);
}
//扩展容量
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
注意:我们不能直接用ptr=realloc(ptr,1000);而是创建一个新的指针变量p来接收新开辟的空间的起始地址,如果开辟成功才让ptr来接收p的地址,因为如果ptr=realloc(ptr,1000),当开辟失败,realloc会返回NULL,ptr就会等于NULL,这样的话,我们不但没有开辟到新的空间,还把原来的旧空间给”弄丢了“。
五、常见的动态内存错误
1、对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
当开辟失败时,p=NULL,就不能进行*p=20这个操作。
正确做法是:
void test()
{
int *p = (int *)malloc(INT_MAX/4);
if(p==NULL)
{
return -1;
}
else
{
*p = 20;
}
free(p);
}
2、对动态开辟空间的越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
我们只开辟了10个int型字节的空间,现在却要访问11个int型字节的空间,这样就越界访问了,这显然是不行的。
3、对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
a不是动态开辟的空间,不能使用free!!free只能用来释放动态开辟的空间。
4、使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
p++之后,指向了下一个int型数据的地址,即跳过了4个字节,如果此时释放,就还有4个字节的空间没有被释放,造成内存泄漏。
5、对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6、忘记free,造成内存泄漏。



