介绍
在C语言中,我们经常会遇到创建变量和数组来解决相应问题编译代码的情况,这时候我们创建的变量大小都是固定的,如果编译要求进行数据扩容,或创建变量过大造成栈空间浪费,且在栈区创建的变量也有大小限制,我如果我们想要一个可以自己进行扩容,针对相应数据创建相应空间的变量,就要用到我们的动态内存管理函数:malloc calloc和realloc
malloc
cplusplus上的定义
动态内存函数需要调用头文件stdlib,malloc是在内存的动态存储区中分配一个长度为size的连续空间。此函数的返回值是分配区域的起始地址,也就是说,他是一个指针函数,所返回的指针指向该分配域的开头位置,是使用最多最普遍的动态内存创建函数
示例
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
从上面的代码可以看到,在创建了动态变量后,代码进行了一次判断,以用来确定该动态内存变量是否创建成功,这是因为可能会出现对NULL指针的解引用操作,所以malloc函数的返回值要判断,以确保变量的成功创建和安全性。在这里我们将malloc创建的20个字节的变量强制类型转化为int*类型,并用整型指针接收。
接下来是malloc的使用:
示例
int i = 0;
for (i = 0; i < 5; i++)
{
p[i] = i + 1;
}
for (i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
//int i = 0;
//for (i = 0; i < 5; i++)
//{
// *(p + 1) = i + 1;
//}
//for (i = 0; i < 5; i++)
//{
// printf("%d ", *(p + 1));
//}
//释放
free(p);
p = NULL;
return 0;
}
在上面的示例中,可以看到我们创建的动态内存变量可以进行正常运用,他可以当成数组进行操作,而在使用完我们创建的变量后,需要手动进行销毁,为什么呢?因为动态内存变量是创建在堆区上的,而堆区中的数据虽然没有规定大小,但是不能自动释放,所以我们需要用到free函数对创建的动态内存进行释放,如果不进行释放,当程序关闭后,系统也会自行进行回收,但不推荐这样做,如果不释放,程序也不结束,会造成内存泄漏,因此,所申请的空间,如果不想使用,需要free函数。在内存进行释放后,我们看到原本用来接收malloc创建的内存的指针p现在变成了一个野指针,众所周知,野指针是非常危险的,所以,我们给p指针赋值一个NULL,让他变成一个空指针,确保安全性。
calloc
cplusplus上的定义
calloc与malloc相似,但是calloc在创建内存时,会将其初始化成0,也就是说:calloc和malloc参数不同,都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0,如果要初始化用calloc
示例
int main()
{
int* p = (int*)calloc(10, sizeof(int));
// 多少个元素为多少
if (p == NULL)
{
printf("calloc:%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p);
p = NULL;//防止野指针
return 0;
}
calloc函数参数也与malloc有些小小的不同,calloc的参数决定了在使用他创建动态内存时,需要指定元素个数,即多少个元素为多少字节,剩下的判断和释放则与malloc相同。
realloc
cplusplus上的定义
realloc与前两个函数不同,他是调整动态内存空间的函数,而不是创建动态内存,当我们的动态内存创建的过大,就可以使用该函数来修改动态内存,值得注意的是,realloc会找更大的空间,而不是在原有空间延续,将原有的数据拷贝到新的空间,释放原有空间,然后返回新空间地址,且如果修改失败返回空指针。
示例
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("malloc:%s", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
p[i] = i + 1;
}
//p = realloc(p, 40);//最好不要用p接受,如果realloc调整失败返回空指针,会丢失原有数据
//用临时指针接受
int* ptr = (int*)realloc(p, 40);
if (ptr != NULL)
{
p = ptr;
for (i = 5; i < 10; i++)
{
p[i] = i + 1;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
}
else
{
printf("%s\n", strerror(errno));
return 1;
}
free(p);
p = NULL;
return 0;
}
从示例中我们可以发现:realloc的返回值不能直接赋值到p中,这是因为如果realloc调整失败返回空指针,赋给p后会丢失原有数据,所以用临时指针接收。
常见错误
越界访问:
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
p[i] = i;
}
//越界访问不可取
free(p);
p = NULL;
return 0;
}
对非动态开辟内存使用free函数
int main()
{
int arr[10] = { 1,2,3,4,5 };
int* p = arr;
free(p);
p = NULL;
return 0;
}
使用free释放一块动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(40);
int i = 0;
if (p == NULL)
{
return 1;
}
for (i = 0; i < 5; i++)
{
*p = i + 1;
p++;
}
//这里释放的是p++过后的地址,不是起始地址
free(p);
p = NULL;
return 0;
}
对一块动态内存多次释放
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
return 1;
}
free(p);
p = NULL;//将其变为空指针后可以再释放
free(p);
p = NULL;
return 0;
}