前言
大家好,很高兴能和热爱知识的你在这里相遇。
下面我们在进入讲解之前我们可以先想一想什么是动态内存,
为什么会需要设置动态内存,
动态内存与我们的数组又有什么区别?
希望大家带着自己的想法进入下面的“副本”开始“刷 怪 升 级 ”。
一、动态内存开辟函数
(一)malloc
void* malloc(size_t size);
size为所开辟空间的字节数。
这个函数向内存申请一块连续的空间,并返回指向该内存的指针,
如果申请内存失败就返回一个NULL指针,因此对malloc函数的使用一定需要进行检测;
返回类型为 void* ,因此malloc函数并不知道开辟内存的类型,需要程序员在使用时进行强制类型转换;
如果size为0,malloc的行为是未定义的,结果取决于编译器。
int main()
{
int* pa = (int*) malloc(sizeof(int) * 10);
//开辟一块大小为40个字节的空间,也可以直接写成 malloc(40)
return 0;
}
对于动态开辟的内存需要我们手动释放,释放操作就需要用到 内存回收函数。
(二)free
void free( void* ptr);
free是专门对动态开辟的内存进行释放和销毁的,
如果ptr不是动态开辟的内存,操作未定义,
如果ptr为NULL指针,函数就什么也不做。
int main()
{
int* pa = (int*) malloc(sizeof(int) * 10);
//开辟一块大小为40个字节的空间,也可以直接写成 malloc(40)
free(pa);
//释放pa所指向的内存空间
return 0;
}
补充:肯定会有小伙伴有疑惑:我们没有对动态开辟的内存进行free好像也不会有任何问题,
没错,如果我们的函数走到最后也没有发现free函数,那么编译器会在结束程序时自动释放动态开辟的内存;
不过,如果我们总是这样做,把自己的事情交给编译器来做的话,那~编译器也不会那你怎么办的,
只是在程序运行结束之前我们动态开辟的内存是不能再次使用的,这样会造成不必要的内存浪费。
(三)calloc
void* calloc(size_t num, size_t size);
num为元素个数,size为一个元素的大小
calloc和malloc的区别就是calloc会将分配的内存全部初始化为0,而malloc只是将内存分配给我们,里面的值还是随机值(或者垃圾值)
int main()
{
int* pa = (int*) calloc(10,sizeof(int));
//同样使用free释放空间,后面的realloc同理
free(pa);
return 0;
}
补充:想要使用malloc达到和calloc一样的效果也很简单,可以使用memset函数将malloc开辟的空间全部设置为0即可。
(四)realloc
void* realloc(void* ptr, size_t size);
realloc函数使动态内存管理更加方便,
当我们开辟的内存过大或过小时都可以使用realloc函数进行调整。
int main()
{
int* pa = (int*)malloc(40);
//如果我们觉得空间太小,就可以对它进行扩容
int* pb = (int*)realloc(pa, 80);
//这里新创建一个指针pb 是为了防止空间开辟失败,使得pa之前存储的数据也丢失
if(pb == NULL)
return 1;
else
pa = pb;
return 0;
}
补充1:realloc如果开辟内存成功就会自动free掉pa之前指向的空间,所以不需要我们再次free
补充2:realloc在进行扩容时,如果之前开辟的内存区域后面的空间足够,就会直接在原位置之后追加,
否则就需要重新开辟一块连续的空间,并将之前空间内的数据复制到新的空间。
图解:
二、常见错误
(一)对NULL指针解引用
int main()
{
int* pa = (int*)malloc(40);
//如果内存开辟失败函数会返回空指针,后面对空指针解引用时会有问题
//if(pa == NULL)
//{
// printf("开辟内存失败\n");
// return 1; // 和return 0;作区分
//}
for(int i=0;i<10;i++)
scanf("%d",pa+i);
for(int i=0;i<10;i++)
printf("%d ",pa[i]);
return 0;
}
(二)对动态开辟空间的越界访问
int main()
{
int* pa = (int*)malloc(40);
if(pa == NULL)
{
printf("开辟内存失败\n");
return 1;
}
//这里越界时编译器可能也不会进行报错
for(int i=0;i<12;i++)
scanf("%d",pa+i);
return 0;
}
(三)对动态开辟内存多次free
int main()
{
int* pa = (int*)malloc(40);
if(pa == NULL)
{
printf("开辟内存失败\n");
return 1;
}
for(int i=0;i<10;i++)
scanf("%d",pa+i);
free(pa);
for(int i=0;i<10;i++)
printf("%d ",pa[i]);
free(pa);//这里对pa进行了两次free
return 0;
}
(四)未在初始位置进行free
int main()
{
int* pa = (int*)malloc(40);
if(pa == NULL)
{
printf("开辟内存失败\n");
return 1;
}
for(int i=0;i<10;i++)
scanf("%d",pa++);// pa 指针已经向后移动了
free(pa);
return 0;
}
(五)对非动态开辟内存进行free
int main()
{
int pa[10]={0};
for(int i=0;i<10;i++)
scanf("%d",pa+i);
free(pa);//pa为数组
return 0;
}
(六)内存泄漏
int main()
{
int* pa = (int*)malloc(40);
if(pa == NULL)
{
printf("开辟内存失败\n");
return 1;
}
for(int i=0;i<10;i++)
scanf("%d",pa+i);
//free(pa);
//就是不对pa指向的空间进行释放
return 0;
}
三、柔性数组
柔性数组是包含在结构体之中的,
1.结构中柔性数组前至少有一个成员,
2.一个结构中只能有一个柔性数组,并且柔性数组需要放在最后,
3.对结构求大小时不包含柔性数组的大小,
4.包含柔性数组的结构需要使用malloc函数分配内存,并且分配的内存应该大于结构体的大小,来适应柔性数组的预期大小。
typedef struct str
{
int data;
char arr[];
}s;
int main()
{
printf("%d\n",sizeof(s));
s* ps = (s*)malloc(sizeof(s) + 100);
//这里给arr分配了100个字节
free(ps);
return 0;
}
总结
下面我们来解答一下前面的几个问题:
1.什么是动态内存?
答:由于堆空间只能在程序运行时被使用,因此堆空间也被称动态内存。另外,动态内存只能在程序运行时通过指针
对分配给各种变量、字符串和数组( 只能通过指针来访问堆空间)。动态分配内存需要使用函数:malloc(),calloc()和realloc().
使用完后还要使用free()函数将内存归还。
2.为什么会需要设置动态内存?
答:在很多情况下我们并不能预先知道需要多大的空间,如果我们一开始就给定数组,那就会出现给的太多,或者空间不足的情况,
而有了动态内存分配,可以让我们需要多少空间就开辟多少,避免了内存浪费和内存不足的情况。
3.动态内存与我们的数组又有什么区别?
答:我觉得第二个的答案也同时适合本题(哈哈,熊猫国庆节也想偷个懒)。
以上就是动态内存相关的简单内容,如果有什么疑问或者建议都可以在评论区留言,感谢大家的支持,欢迎来评论区一起探讨,大家的鼓励是继续更新的巨大动力。