C语言的动态内存管理这个概念是我们第一次接触,想要学好动态内存,学好malloc、calloc、relloc、和free这四个函数是必不可少的。
目录
1.为什么存在动态内存分配
2.动态内存函数介绍
2.1malloc函数(申请一块内存空间)
2.1.1函数介绍
2.1.2代码示例
2.2free函数(释放申请的动态内存的空间)
2.2.1函数介绍
2.2.2代码示例
2.3calloc函数(申请一块内存空间)
2.3.1函数介绍
2.3.2代码示例
2.4realloc函数(对动态开辟的内存大小进行调整,即重新分配内存,它也可以用来开辟空间)
2.4.1函数介绍
2.4.2代码示例
2.4.3realloc函数用来开辟的情况
在了解动态内存之前,我们要知道数据被申请后存储在内存哪里,如图所示:
1.我们平时的内存开辟方式是在栈空间上开辟空间,这种开辟方式有两个特点:
空间开辟大小是固定的;
数组在声明的时候,必须指定数组的长度,它所存在的内存在编译时分配;
2.但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在运行的时候才会知道,那数组的编译时开辟空间的方式就不能满足了
这时候就需要我们动态内存开辟。
1.头文件:#include
2.函数格式:void *malloc( size_t size );
3.malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针(首地址)
4.如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
5.返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定,通常用到强制类型转换
6.如果size为0个字节,malloc函数的行为是标准未定义的,取决于编译器,同时,这也是无意义的
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
int main()
{
//申请40个空间的字节,用来存放10个整型
int* p = (int*)malloc(40);
//判断malloc函数是否开辟空间成功,如果成功,就往下执行程序;
//如果失败,打印错误信息,并返回,结束代码
//注意,这里开辟一块空间并不是百分百成功的,假若我们要申请一个无限大的空间,
//malloc函数是无法开辟的,就会打印错误信息,打印Not enough space
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用动态开辟的这块内存
int i = 0;
for (i = 1; i <=10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
//这里是动态开辟内存的释放,我们在free函数中仔细说明
free(p);
p = NULL;
return 0;
}
代码中,我们看到了free函数的使用,下面我们来介绍free函数
free函数和malloc、calloc、realloc都是成对使用的
1.头文件:#include
2.函数格式:void free( void *ptr);
3.free函数专门用来释放和回收动态内存
4.如果参数ptr指向的空间不是动态内存开辟的,那么free函数的行为是未定义的,free函数是无法释放非动态内存开辟的空间的
5.如果ptr此时是NULL指针,那么free函数什么也不做
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
int main()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 1; i<=10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
//free函数,归还malloc开辟的空间
free(p);
p = NULL;
return 0;
}
free函数主动归还malloc所开辟的空间但此时,p就变为了野指针,还存放这开辟空间的首地址,没有主动置空,所以需要我们把它置为空指针,防止非法访问
malloc函数和calloc函数都是申请一块内存空间,用法基本相同,只有细微差别
malloc函数申请到的空间没有初始化,直接返回起始地址;
calloc函数申请好空间后,会把空间初始化为0,然后返回起始地址
1.头文件:#include
2.函数格式:void *calloc( size_t num, size_t size );
3.num是要开辟元素的个数,size是开辟的每个元素的大小,也就是为num个大小为size的元素开辟一块内存空间,并把空间的每个字节初始化为0
int main()
{
//为10个大小为int型的元素开辟一块空间
int* p = (int*)calloc(10, sizeof(int));
//判断calloc函数是否开辟空间成功
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i <10; i++)
{
//在这里直接打印,可以验证一下是否calloc函数把开辟好的空间初始化为0
printf("%d ", *(p + i));
}
//free函数释放这块空间
free(p);
p = NULL;
return 0;
}
当我们发现动态申请的空间太小或者太大时,为了合理使用内存,我们要对内存的大小做灵活的调整,realloc函数就可以做到对动态开辟内存大小的调整
1.头文件:#include
2.函数格式:void *realloc( void *ptr, size_t size );
3.realloc函数的出现让动态内存管理更加灵活
4.ptr是要调整的内存地址,size是调整之后的新大小,扩容后空间的总大小是整个动态开辟空间的总大小
5.函数返回值为调整之后内存起始位置,并不初始化
6.函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
7.realloc在调整内存空间时存在两种情况:
待扩容的动态内存后有足够的空间
当它后面有足够的空间可以扩容时,直接在后边续上新的空间,返回旧的起始地址
待扩容的动态内存后没有足够的空间
当后面没有足够空间可以扩容时,realloc函数会找一个满足空间大小的新的连续空间,把旧的空间的数据,拷贝到新空间前面的位置,并把旧的空间释放,同时返回新空间的首地址
我们用代码来讨论
//realloc函数
int main()
{
//先使用malloc开辟一块大小为5个整型的空间
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
//此时,我们发现空间不够,想要在开辟好的动态空间之后再增加5个整型空间
//注意,这里扩容空间的总大小是整个动态开辟空间的总大小
int* ptr = (int*)realloc(p, sizeof(int) * 10);
//这里必须用一个新指针来接收realloc的返回值,不能用P,因为realloc函数可
//能会扩容失败返回NULL,若用原来p指针接收,会把未扩容前已经放入空间的
//数据丢失,不安全
if (ptr != NULL)
p = ptr;
//使用扩容后的空间
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
我们发现打印出来的是我们最先存入的5个数据和5个随机值,也验证了realloc函数对所扩容出来的空间不进行初始化
当函数传递的第一个参数为空指针(NULL)时,函数作业相当于malloc,向内存开辟一块动态空间,即:
int* p=(int*)realloc(NULL,40);
相当于
int* p=(int*)malloc(40);
这就是动态内存管理的基本知识,我们会在下期学习动态内存管理的一些需要注意的地方,我们下期再见!!!