前言
给数组分配多大的空间?
你是否和初学C时的我一样,有过这样的疑问。
这一期博客就来聊一聊动态内存的分配
读完这篇文章,你可能对内存的分配有一个更好的理解!
文章目录
一、为什么要存在动态内存管理?
我们知道int val = 20;
在栈空间上开辟四个字节。char arr[10] = {0};
在栈空间上开辟10个字节的连续空间;但是上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。这时候就衍生了动态开辟空间,也就是动态内存管理,我们一起往下看他究竟是如何使用的。
二、 动态内存函数的介绍
2.1动态内存的好处
<1>.可以控制内存的大小
在很多时候,我们申请的空间是未知的,就比如说通讯录的使用就存在一个问题,你定的空间需要多少个字节?当申请空间的太少,就有可能出现满员情况,如果开辟的空间过大,又会造成浪费,很难取到一个合适的值。
在动态内存分配就可以避免这个问题,你可以运用 reallac
(下文会介绍)控制大小,当内存达到申请的空间时,就会主动扩容,也就是再次向内存申请空间。
<2> 可以多次利用这部分空间
静态内存分配利用的空间,整个程序结束才会释放给系统,而动态内存分配的空间,只能在函数运行结束后由系统自动释放,需要用户主动去释放,可以在使用结束(就比如说打印元素结束后),用户再通过 free函数释放这块空间,当再次用动态内存申请空间时,就可以再次利用这块空间,这样也能在一定程度上,可以节省一定的空间。
<3>不占用栈区的内存
假设栈区定义了变量,而每个变量分配内存时,之间又有一定的间隙,当定义的变量足够多时,空隙也会很多,这时候向系统申请一个比较大且连续的空间时,虽然有足够的空间,但是缺少了连续的空间。
所以动态内存在堆区申请,就完全不必担心栈区的空间不够的问题
2.2malloc和free以及使用实例
C语言提供了一个动态内存开辟的函数malloc
:
>void* malloc (size_t size);
功能:
向内存申请一块连续可用的空间(大小为size字节),此存储空间中的初始值不确定
返回值:
若分配成功,则返回一个指向已分配的空间开头的指针;若分配失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
说明:
1.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己
来决定。
2.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
C语言提供了另外一个函数free
,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
注:malloc和free都声明在 stdlib.h 头文件中。
动态开辟空间实例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = NULL;
ptr = (int*)malloc(num * sizeof(int));
if (NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for (i = 0; i < num; i++)
{
*(ptr + i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//防止野指针问题出现
return 0;
}
2.3calloc函数及使用实例
C语言还提供了一个函数叫 calloc
, calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
这个函数我们不做详细介绍(与malloc高度相似),我们在此只说明malloc与calloc函数的区别:
calloc函数开辟的空间,会将空间的内容全部初始化为零,而malloc函数向系统申请的空间,空间的值都是随机的
根据calloc的这一特性,可以很方便的开辟一些需要初始化的数据
。
我们看个实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if(NULL != p)
{
//使用空间
}
free(p);
p = NULL;
return 0;
}
2.4realloc函数及使用实例
博主认为:有了realloc函数才是真正的动态开辟内存
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc
函数就可以做到对动态开辟内存大小的调整,realloc函数的出现让动态内存管理更加灵活。
函数原型如下:
void* realloc (void* ptr, size_t size);
功能说明:
先判断当前的指针是否有足够的连续空间,如果有,扩大ptr指向的地址,并且将ptr返回,如果空间不够,先按照size指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来prt所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。
返回值:
如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL
注:扩容过大,重新开辟也不够导致的扩容失败,就会导致扩容前申请的空间也发生改变,所以不能直接用ptr来重新赋值,防止先前的数据丢失
。
我们来看实例:
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* a; int i = 0;
a = (int*)calloc(10, sizeof(int));
if (a == NULL)
printf("分配失败");
else
{
for (i = 0; i < 10; i++)
{
*(a + i) = i;
printf("*a=%d\n", *(a + i));
}
//需要扩容
int* ret = realloc(a, 80);
if (ret != NULL)
{
a = ret;
}
free(a);
a = NULL;
}
}
三、3. 常见的动态内存错误
3.1对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果开辟失败,p的值是NULL,就会有问题
free(p);
}
3.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);
}
3.3对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//what???
}
3.4 使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
3.5对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
3.6动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
注:动态开辟的空间一定要释放,并且正确释放 。
欢迎点赞收藏加关注,如若有问题可以评论区留言或者私聊博主呦!