(文章目录)

前言

  <font color = green > 🌈hello! 各位宝子们大家好啊,又是新的一天开始了,今天给大家带来的是动态内存规划这一章节!</font>   <font color = green > ⛳️我们在创建变量的时候大家都知道大小是固定,不够灵活。而动态内存分配可以改变这一现象!当我们需要多少就可以规划多少,而不需要时就可以释放掉,这样是不是就可以极大地避免了内存的浪费!</font>   <font color = Orange > 📚本期文章收录在[《C语言高阶篇》],大家有兴趣可以看看呐!</font>   <font color = purple>:tent: 欢迎铁汁们 :heavy_check_mark: 点赞 👍 收藏 ⭐留言 📝!</font>

💬 为什么存在动态内存分配

  ⛳️在前面内容中我们学的开辟空间大多都是用数据类型直接创建空间。

  • <font color = purple>比如用整形开辟一个大小为4个字节的空间</font>
  • <font color = purple>或者数组开辟一个连续的储存空间</font>
  • 而这些临时变量大多都是存放在栈区的 🔥 ==<font color = red>注:在前面C/C++中内存大致分的三个区域有讲过</font>== [《C/C++的三个内存区域》]
int main()
{
	int a = 0;//在栈空间上开辟四个字节
	int arr[40]={0};//在栈空间上开辟40个字节
}

但是这的开辟空间的方式有两个缺点:

  • 数组空间申请多了,如果没有用完就会照成空间的浪费!
  • 空间开辟大小是固定的

所以像以前的空间开辟方法满足不了我们的需求,那么有没有我们想开辟多少空间就开辟多少,而当我们不想要的时候还可以释放!这个时候就需要动态内存开辟了!

💬 动态内存函数的介绍

  ⛳️ 而动态内存开辟就需要用到相关的函数分别是: malloc free calloc realloc 把这四个函数只要掌握就可以完全的掌握动态内存分配了,下面我们就详细给大家介绍介绍:

1️⃣ 动态内存函数 malloc

动态内存开辟的函数: malloc

void* malloc (size_t size);

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size0malloc 的行为是标准是未定义的,取决于编译器。

⛳️ 好了malloc的使用方法给大家介绍了,接下来就是给大家介绍介绍这个这个函数如何使用:

  • <u>他们的库函数都是</u> #include <stdlib.h>
  • ==所以使用的时候一定要记得加头文件哦!==
#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	malloc(40);
	return 0;
}

我们都知道数组创建的空间是连续,而malloc申请的空间也是连续的但是malloc的空间是没有类型的。

  • 那么我们想像数组一样访问整形4个字节来访问怎么办呢?
  • 很简单我们把 malloc 的返回值类型强制转换为 int*
  • 整形指针接收 malloc 的返回值就可以
#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p=(int*)malloc(40);
}

这样我们就可以和整形数组一样存放整形了,因为指针解引用每次也跳过4个字节

💭 malloc 函数返回失败怎么办

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

  • 如果开辟失败,就会给 p 返回NULL 空指针
  • ==而我们一旦对空指针在进行访问不会,越界访问越界了嘛?==
  • ==而这是绝对不允许的,一旦越界就会导致程序崩溃⁉️==
  • 所以我们加一段代码来保证程序的安全性
int  main()
{
	int arr[10] = { 0 };
	int* p = (int*)malloc(40);
	//开辟失败
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	return 0;

这样就就可以在开辟失败时及时避免错误,直接return返回让程序结束!

  • 这里开辟失败是,让库函数 perror 给我们提示一下
  • malloc 里面出现了什么错误!
  • 下面就给大家观察一下开辟失败是什么样的

📑图片展示: 在这里插入图片描述

⛳️ 大家看这里当我们申请的空间太大是开辟不了就会给我们返回空间不够的错误提示

  • ==ps:申请的空间一定要非常大不然测试就不会返回错误值的==
  • ==博主试了好几遍还以为是自己的代码问题结果是申请空间太小了==

💭 malloc 是在哪里开辟空间的

⛳️我们都知道临时变量是存放在栈空间的,那么malloc申请的空间是哪里的呢?

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = (int*)malloc(40);
	//开辟失败
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	 
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	return 0;
}

📑 代码结果: 在这里插入图片描述

  ⛳️这里打印的就是我们申请空间的值,但由于malloc函数并不会给我们初始化所以里面存放的都是随机值。

  • <font color="#006666">那么这里面的动态内存分布到底是什么样呢?</font>
  • <font color="#006666">为什么里面全部都是随机值呢?</font>
  • ==<font color = red>这个图片来告诉你一切</font>==

在这里插入图片描述

  ⛳️我们动态内存分配都是在堆区开辟空间的, p 指针变量是在栈区里面开辟的空间里面。所以当malloc在返回时返回了起始地址然后我们用 p 接收了malloc申请空间的起始地址

  • ==但是,malloc这个函数只返回起始地址并不进行初始化==

💭 malloc申请空间为0

  ⛳️ 做为一个程序员我们在想要申请空间的时候肯定是已经知道,要申请多少空间。你又要malloc申请空间,又只申请0个空间,这种行为本来就是不合理,所以我们在使用malloc时要避免这种情况以免出现不必要的错误!

  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
📆 malloc申请空间会主动释放嘛

  ⛳️而malloc申请的空间,当程序退出时,才会还给操作系统,而当程序未结束时,动态内存申请的内存空间,是不会主动释放的。这样就会照成内存的浪费!

  • ==这时就需要使用free来释放,我们申请的动态内存空间==
  • ==编程的好习惯是,每次使用完malloc都要使用free释放空间==
  • 下面我们就来介绍一下free函数

2️⃣ 动态内存函数 free

  ⛳️C语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:

  • <kbd>void free (void* ptr);</kbd>

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

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

⛳️ 好了free的参数详情给大家介绍了,接下来就是给大家介绍介绍这个这个函数如何使用:

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = (int*)malloc(40);
	//开辟失败
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	 
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

  ⛳️ 这就是 free 的使用方法了,是不是非常简单。只需要把我们指针变量 p 传给 free 函数,因为 p 里面存放了 malloc 申请空间的起始地址,那么为什么还要把 p 给置为空指针呢?

  • 因为我们虽然把指针p记录的动态空间给释放了
  • 但是p本身不会被释放,而p里面存放的地址就成 ==野指针==!
  • 这个情况是非常不安全的所以我们把它置为空!
💭 内存函数 free的错误使用

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

  • 这种行为是不被允许的,希望大家使用时注意!

📚 代码演示:

int main()
{
	int a = 10;
	int* p = &a;
	free(p);//err
	return 0;
}

📑 代码结果: 在这里插入图片描述

3️⃣ 动态内存函数 calloc

  ⛳️ C语言还提供了一个函数叫 calloccalloc 函数也用来动态内存分配。其实非常简单这个函数和 malloc 的功能是一样的,只不过会把申请的空间初始化为 0

函数原型如下:

void* calloc (size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

📜举个例子:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = calloc(10,sizeof(arr[0]));
	//开辟失败
	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的使用大致一样!好的习惯是每次申请的动态空间在用完的时候都要释放掉!

4️⃣ 动态内存函数 realloc

  ⛳️有人会说不是动态内存可大可小嘛?想要多少空间就要多少,不想要了就可以缩小!前面的函数只能开辟和释放并不能控制大小啊?下面我们就给大家介绍介绍realloc函数它就完美的实现了这些功能。

  • ==而想熟练的使用realloc函数就得知道==
  • ==realloc开辟内存的三种情况==

realloc 函数函数原型如下:

void* realloc (void* ptr, size_t size);

💭 内存函数 free的参数说明

realloc函数的出现让动态内存管理更加灵活: <kbd>void* realloc (void* ptr, size_t size);</kbd>

  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • <font color="#006666">返回值为调整之后的内存起始位置。</font>
  • <font color="#006666">这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。</font>

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = calloc(10,sizeof(arr[0]));
	//开辟失败
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	//增加空间
	realloc(p, 80);

	free(p);
	p = NULL;
	return 0;
}

这就是realloc的用法当我们说开辟的空间只有40个大小不够用了。那么就可以用realloc去增加为80个字节大小!

💭 内存函数 free的3种使用情况

✅情况一

⛳️ 第一种情况就是后面的连续空间足够,我们我们就会在后给连续的新开辟40个字节使其增加为80个字节大小!

  • 下面给大家看看看内存分布情况图

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = calloc(10,sizeof(arr[0]));
	//开辟失败
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	//增加空间
	realloc(p, 80);

	free(p);
	p = NULL;
	return 0;
}

📑图片展示:

这就是第一种情况的内存分布,当后面的空间足够时后给连续的新开辟40个字节使其增加为80个字节大小

在这里插入图片描述

✅情况二

  ⛳️当我们想用reaclloc增加空间时,但是后面空间不够了就会重新开辟新空间并将原来空间的内容拷贝到新空间,并且将旧空间释放掉.

在这里插入图片描述 这里我们思考一个问题,realloc也会返回失败那么就会返回NULL空指针!

  • ==本来我p指针变量还维护40个字节的大小结果你给一个空指针==
  • ==那么我不仅新空间没开辟,旧空间也丢了,==

这样就会造成内存泄漏的问题,所以我们在这里就不敢用p接收我们的realoc返回值,需要进行代码改进!

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = calloc(10,sizeof(arr[0]));
	//开辟失败
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	//增加空间
	int* ptr = realloc(p, 80);
	if (ptr != NULL)
	{
		p = ptr;
		ptr=NULL;
	}
	else
	{
		perror("realloc");
	}

	free(p);
	p = NULL;
	return 0;
}

这样就可以避免我们上面说的错误了,如何 realloc 开辟失败我们就不接收空指针。

  • if判断完了之后再决定接不接收就完美解决问题
✅ realloc如何减少空间

  ⛳️ 这个不就更加简单了嘛?前面说了我们realloc函数可以动态开辟空间可大可小!那么使动态内存变小不就是更加简单了嘛?直接把内存改小不就行了.

  • 下面就来演示一下如何使动态内存变小

📚 代码演示:

#include <stdio.h>
#include <stdlib.h>
int  main()
{
	int arr[10] = { 0 };
	int* p = calloc(10,sizeof(arr[0]));
	//开辟失败
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//减少空间
	int* ptr = realloc(p, 20);
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		perror("realloc");
	}

	free(p);
	p = NULL;
	return 0;
}

📑 代码结果: 在这里插入图片描述

⛳️这里就可看到我们把malloc申请的动态空间40个字节,改变成了20个字节!

✈️ 总结

✅ 归纳: 好了以上就是关于动态内存分配函数 malloc free calloc realloc 4个动态内存分配函数的全部用法了!   malloc的介绍和使用方法   free的介绍和使用方法   calloc和malloc的区别   realloc语句使用的2种情况 :cloud: <font color="#006666">把这些内存函数掌握完,你就可以完美的使用动态内存分配了快去试试吧!</font> <font color ="7B68EE">看到这里了还不给博主扣个: ⛳️ 点赞:sunny:收藏 :star: 关注</font> 💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖 <font size = 5 face = "华文彩云" color =red>拜托拜托这个真的很重要!</font> <font color="#660066">你们的点赞就是博主更新最大的动力!</font> <font color="#660066">有问题可以评论或者私信呢秒回哦。</font> 在这里插入图片描述