动态内存管理【存稿】

动态内存管理

1.为什么有动态内存分配?

我们所知道的内存开辟

int a = 3; //在栈空间上开辟4个字节
char b = 'a'; //在栈空间上开辟1个字节
int arr[30] = {0};//在栈空间上开辟120个字节的连续空间

这种内存开辟的特点:

  • 空间开辟的大小是固定的

  • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小就无法调整了

    • 那么变长数组的长度不是可以存变量吗?那么变长数组是不是就能调整大小了?
      • 答案是不可以,变长数组只是说数组的大小可以使用变量指定,但是一旦数组创建好了之后,依然是无法调整大小

局限性:
上面已经说了,数组在申明的时候必须指定数组的长度,数组的空间一旦确定了大小就无法调整。也就是说:如果创建了一个结构体数组,数组每个元素都是一个人的信息,而该数组能存放100个人的信息。但我仅需要存放3个人的信息,对于这个结构体数组来说,剩余的97个空间就浪费了,同理我需要存放120个人的信息,对于该数组来说是放不下的,而我又不能对其大小进行修改。

因为上述申请空间的大小无法灵活调整,C语言就给了:动态内存管理,给程序员权限,自己申请,自己使用,使用完后,自己释放

需要知道的是动态内存分配是在堆区开辟空间,上述的内存开辟是在栈空间开辟的。如图:
在这里插入图片描述

注意:堆区的空间是能修改的,而栈区的空间是不能被修改的。

2.动态内存管理函数

2.1 malloc和free

通常搭配使用

2.1.1 malloc函数

malloc函数是一个动态开辟内存的函数

void* malloc(size_t size);

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针

  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

  • 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定

    • 因为malloc只知道你要开辟多少个字节空间,但是你要用开辟这么多的字节空间干什么,malloc是不知道的,所以返回void*

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

  • szie_t size:决定malloc开辟空间的字节大小

    • 如果参数size为0,malloc的行为是标准为定义的,取决于编译器

2.1.2 free函数

free函数是专门做动态内存的释放和回收的

void free(viod* ptr);

free函数用来释放动态开辟的内存

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

malloc和free都声明在<stdlib.h>中

下面使用一下malloc和free函数

#define CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
    //申请空间
	int* p = (int*)malloc(10 * sizeof(int));

	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
    
    //使用空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}

	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	
    //释放空间
	free(p);
	p = NULL;

	return 0;
}
  • return 1?
    • return只要不返回0都表示异常返回。
  • 此时释放完要置为空指针,为什么呢?
    • 因为执行free§之后,p指向的内存空间就还给操作系统了,但是p还记得p指向内存空间的起始地址,此时p就是野指针了

2.2 calloc函数

calloc函数也是用来动态内存分配的

void* calloc(size_t num, size_t size);
  • 函数的功能:开辟一个数组,数组的大小是num个元素,每个元素的大小都是size个字节,并把空间的每个字节都初始化为0

  • 与malloc的关系

    • 相同性
      • 跟上述malloc的特点基本上相同(功能:开辟、开辟成功、开辟失败、返回类型)
    • 区别性
      • 参数不同
        • malloc的参数只有size
        • calloc的参数有num、size
      • 功能不同
        • calloc会在返回地址之前把申请的空间的每个字节初始化为全0
          • 三步走
            • 申请空间
            • 把空间的内容全部初始化为全0
            • 返回起始地址
        • malloc申请的空间不会初始化
          • 两步走
            • 申请空间
            • 返回起始地址
  • 什么时候用calloc和malloc?

    • 如果只是想开辟空间并不需要初始化->malloc
    • 如果申请的空间想初始化为0 -> calloc

事实上是否如此呢?

我们用代码说话

#define CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int * p = (int*)calloc(10, sizeof(int));

	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

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

	free(p);
	p = NULL;


	return 0;
}

运行结果如下
在这里插入图片描述

如果改成malloc呢?

#define CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int * p = (int*)malloc(10 * sizeof(int));

	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

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

	free(p);
	p = NULL;


	return 0;
}

运行结果如下
在这里插入图片描述

这就验证了上面的结论。

2.3 realloc函数

realloc函数:使动态内存管理更加灵活

假如我发现我开辟了10个整型的内存空间,实际上只用4个,那么就会发现申请的空间大了,假如我开辟了10个整型的内存空间,实际上要用15个,那么就会发现申请的空间太小了,为了合理的运用内存,我们一定要对内存的大小做灵活的调整
这不,realloc函数就是针对这个来的

realloc函数的功能:调整动态分配内存的大小,实际上是调整申请堆上的内存

void* realloc(void* ptr, size_t size);
  • ptr是要调整的内存地址
  • size调整之后的新大小
  • 返回值为调整过后的内存起始位置
  • 这个函数在调整原内存空间大小的基础上,还会将原来的内存中的数据移动到新的空间
  • realloc在调整内存空间时存在两种情况(在扩容时的2种常见情况)
    • 原有空间之后有足够大的空间
    • 原有空间之后没有足够大的空间

下面进行realloc两种常见情况的讲解

2.3.1情况1 原有空间之后有足够大的空间

当原有空间之后有足够大的空间时,拓展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化

在这里插入图片描述

#define CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int * p = (int*)malloc(5 * sizeof(int));

	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

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

	int * ptr = (int*)realloc(p, 5 * sizeof(int));

	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
	else
	{
		perror("realloc");
		return 1;
	}

	for (i = 5; i < 10; i++)
	{
		*(p + i) = i + 1;
	}

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

	free(p);
	p = NULL;

	return 0;
}

此时调试一下看一下

在这里插入图片描述

此时断点打到这里,此时ptr还没创建

在这里插入图片描述

此时ptr已经创建了,很明显,此时原有空间空间足够,直接在原有内存追加空间,返回原有空间的起始地址

2.3.2 情况2 原有空间之后没有足够大的空间

当原有空间之后没有足够大的空间,扩展的方法就是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址

假如我要开辟40000个字节空间

在这里插入图片描述

此时ptr所指向的起始地址,并不需要我们自己去free掉,realloc函数自己就会把原有的空间(ptr指向的空间)给free掉

#define CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int * p = (int*)malloc(5 * sizeof(int));

	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

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

	int * ptr = (int*)realloc(p, 10000 * sizeof(int));

	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
	else
	{
		perror("realloc");
		return 1;
	}


	free(p);
	p = NULL;

	return 0;
}

调试来看看

在这里插入图片描述

此时ptr还没创建

在这里插入图片描述

此时ptr已经创建完成了,可以看到,ptr现在所指向的空间已经不一样了,说明现在是情况2

在这里插入图片描述

此时走完if语句,p被赋值为ptr所指向的新的开辟的内存空间的起始地址,ptr已经被置为空,而ptr所指向的空间已经自动被realloc释放掉了
注意:把p的值赋给ptr的时候,不能free(ptr),因为此时ptr和p都指向同样的起始地址,如果free(ptr)就会把整块空间(包括原有的数据)给free掉,这样什么都没有了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤言o 0 O

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值