为什么要动态内存管理?
在我们写程序前不确定要多大的内存时,或者当开静态空间开大了浪费,开小了不够用的情况。使用动态内存管理能更加灵活,可以按需调整内存大小。
动态内存的存储位置:
动态内存存储在堆区(Heap),这个区域由我们程序员来进行内存空间的申请和释放。
动态分配内存的函数(C语言):malloc、calloc、realloc、free 包含在头文件:stdlib.h
C++提供了新的操作符来方便我们操作
先介绍C语言动态管理内存的方式:
1、malloc和free
1、malloc :
语法形式:
void* malloc(size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查
- 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己决定
- 如果参数size为0,malloc的行为是标准未定义的,取决于编译器
2、free:
free是专门用来做动态内存的释放和回收的
语法形式:
void free(void* ptr);
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么都不做
比如:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int num = 0;
scanf("%d",&num);
int arr[num] = {0};
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));//向内存申请空间
if(ptr == NULL)//申请失败就返回
{
return 1;
}
else
{
int i = 0;
for(i=0;i<num;i++)
{
*(ptr + i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;
return 0;
}
2、calloc和realloc
这俩也用来分配动态内存
1、calloc:
语法形式:
void* calloc(size_t num,size_t size);
- 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
- 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0
比如:
2、realloc:
语法形式:
void* realloc(void* ptr,size_t size);
realloc函数可以对动态开辟内存的大小进行调整。
- ptr是要调整的内存地址
- size调整之后的大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
- realloc在调整内存空间的时候存在两种情况:
情况一:原有空间之后有足够大的空间
情况二:原有空间之后没有足够大的空间
当是情况一时,要扩展内存就直接在原内存之后直接追加空间,原来空间的数据不发生变化。
当是情况二时,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
int n, i;
// 初始分配内存,存储5个整数
n = 5;
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL)
{
printf("内存分配失败!\n");
return 1;
}
// 给数组赋值
for (i = 0; i < n; i++)
{
ptr[i] = i + 1;
}
// 打印初始数组
printf("初始数组:");
for (i = 0; i < n; i++)
{
printf("%d ", ptr[i]);
}
printf("\n");
// 重新分配内存,扩展到10个整数
n = 10;
ptr = (int*)realloc(ptr, n * sizeof(int));
if (ptr == NULL)
{
printf("内存重新分配失败!\n");
free(ptr); // 释放内存
return 1;
}
// 给新分配的空间赋值
for (i = 5; i < n; i++)
{
ptr[i] = i + 1;
}
// 打印扩展后的数组
printf("扩展后的数组:");
for (i = 0; i < n; i++)
{
printf("%d ", ptr[i]);
}
printf("\n");
// 释放内存
free(ptr);
return 0;
}
开动态内存注意(易错点):
- 忘记检查内存分配是否成功
- 忘记释放内存导致内存泄漏(动态分配的内存需要手动释放。如果忘记释放,程序运行时会占用越来越多的内存,最终可能导致系统资源耗尽)
- 重复释放同一块空间
- 访问已经释放的内存(野指针)
3、c++内存管理方式
因为C++兼容C语言,所以C语言里进行动态内存管理的函数在C++也能用。不过C++提供了更加方便的操作符new和delete。
3.1、new、delete操作内置类型
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
注意:new和delete,new[]和delete[]匹配起来使用
3.2、操作自定义类型
跟C语言不同的点在于,C++在new时会调用构造函数,在delete释放空间前会先调用析构函数
C++内存申请失败时,会抛异常。
malloc/free和new/delete区别:
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个 对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理