动态内存管理

目录

  • 为什么存在动态内存分配
    • malloc和free
  • calloc
  • reallc
  • 使用free释放动态开辟内存的一部分
  • 动态内存开辟忘记释放

为什么存在动态内存分配

目前我们知道的内存分配方式主要有这样的形式

int a = 10; // 4 byte
int c[10];// 40 byte

问题在于一旦我们将内存分配完后代码就写死了,很难再去按照我们的需求去改变所需的内存空间,所以c语言就给了我们一种方便的功能供我们按照需求去申请空间

malloc和free

我们可以使用stdlib.h或malloc.h头文件,但我们一般使用stdlib.h

c语言提供了动态内存分配的函数

void* p = malloc(size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

1.如果开辟成功,则返回指向该空间的指针
2.如果开辟失败,则返回NULL,因此malloc返回值一定要检查
3.返回值类型时void*因此malloc函数并不知道开辟空间的类型,具体在使用时使用者自己来决定
4.如果size为0,malloc函数的行为是未定义的,具体由编译器决定 让我们先看一段代码

    //void* p = malloc(40);
	int* p = (int*)malloc(40);

用malloc函数开辟的内存类型为void*,但在使用时为我们会发现void*类型的指针没法使用,所以在实际操作时,我们作为代码的编写者往往会将该指针强制类型转换为我们所需要的类型

我们在使用此函数时要注意开辟失败的情况,所以我们在使用指针时可以这样设计代码

int* p = (int*)malloc(40);
	//如果开辟成功
	if (p == NULL)
	{
     
		return -1;
	}
	//开辟成功

当我们想要给空间初始化时也很简单

int i = 0;
	for (i = 0; i < 10; i++)
	{
     
		*(p + i) = 0;
	}

现在让我们看看如何释放空间

free(p);

当我们使用完p指向的空间时,我们就可以将空间释放,将内存还给操作系统,而当我们将内存释放后我们还要注意一件事,让我们先来看看代码的调试结果
在这里插入图片描述

上图是刚为p开辟空间时p指向的地址,让我们再看看将p指向空间释放掉后p指向的空间
在这里插入图片描述

与刚刚p指向的地址完全相同,所以说free可以将p指向的地址释放掉,但不会改变p的值,就好像我们去旅馆开房,当我们退房后却没有把钥匙还了,显然这是一件很危险的事情,所以当我们将空间释放后我们往往会把p设为空指针

p = NULL;

如果free函数指向的空间不是动态开辟的,那么free函数的行为是未定义的
而如果free指向的是NULL指针,则函数什么都不做

free(NULL);

所以上面的代码是正确的,只是该函数不进行任何操作

calloc

首先,malloc函数和calloc函数都是用来开辟动态内存的,那么calloc和malloc有什么区别呢?让我们先看看calloc的定义吧
动态内存管理_第1张图片

我们发现calloc会开辟一个数组并将每个元素初始化为0,而malloc不一样,他只会申请一块空间,并将初始地址返回。那么calloc定义中的num和size分别代表什么呢,我们也可以在msdn看看
动态内存管理_第2张图片
num代表元素的个数,size代表每个元素有几个字节,如果我们想开辟一段有10个int类型元素的数组,我们该如何使用calloc呢?

int* p = (int*)calloc(10, sizeof(int));

当然我们也得注意内存分配失败的情况,所以我们还得加上这样的代码

if (p == NULL)
	{
     
		printf("%s", strerror(errno));//打印错误信息
		return -1;
	}

我们也可以把数列的元素都打印出来

	int i = 0;
	for (i = 0; i < 10; i++)
	{
     
		printf("%d ", *(p + i));
	}

在这里插入图片描述
所以使用时我们应该怎么在malloc和calloc之间选择呢?很简单,当我们想给内存初始化时就用calloc,不想初始化时就用malloc

reallc

有时我们会发现内存空间分配太大了,有时又会发现内存空间分配太小了,而realloc的出现可以让内存调整更加方便,首先让我们看看msdn中对realloc的定义
动态内存管理_第3张图片
realloc的第一个参数是我们想要修改内存的指针,第二个是我们想修改成的大小,单位是字节,所以如果我们想把之前的p修改为20个整型的大小应该怎么做呢?我们会发现内存调整会有两种情况,在这里我们第一次p指向的空间为10个整形也就是40个字节,第二次想给他调整为20个整形也就是80个字节,我们先来看两张图
动态内存管理_第4张图片

当我们想扩大空间时,会遇到原空间后空间充足与不足两种情况,当空间充足时则直接在后面开辟内存,若不足则另寻一片空间能使内存连续开辟。现在我们再来看看,我们要如何用代码实现内存变化

int* ptr = (int*)realloc(p, 20 * sizeof(int));
	if (ptr != NULL)
	{
     
		p = ptr;
	}

这里我先用一个临时指针ptr而不是直接用p来接收realloc的返回值,原因在于如果该内存改变失败它将返回一个空指针,如果直接用p接受的话就会让我们连原来的空间都找不到了,所以我们先用ptr来接收,如果开辟成功,也就是说ptr不等于空指针,则将p赋值为ptr。

使用free释放动态开辟内存的一部分

我们先来思考一下这样一段代码是否正确

	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
     
		return -1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
     
		*p++ = i;
	}
	free(p);
	p = NULL;

这段代码显然是错的,因为我们会发现当我们使用free函数的时候,p的值已经发生改变了,而且就算我们只将p增大5个int大小也是错误的,所以当我们使用free时必须从开辟空间的初始位置开始

动态内存开辟忘记释放

我们释放动态内存有两种方法,
1.我们主动用free释放掉
2.当程序结束时系统自动将其释放
所以在有些时候忘记释放内存不会造成太大的影响,但是往往我们会让一个程序不断地运行,导致这一块不需要的空间不被释放,以至于无法使用,造成内存泄漏,所以当我们开辟动态内存后必须将其释放

你可能感兴趣的