编程之路,从0开始:动态内存管理

Hello,大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路。

我们今天来学习C语言中的动态内存管理。

目录

1、为什么要有动态内存管理?

2、malloc和free

(1)malloc函数

(2)free函数

3、calloc和realloc

(1)calloc函数

(2)realloc函数

4、常见的动态内存的错误

(1)对空指针的解引用问题

(2)对动态开辟空间的越界访问

(3)对非动态开辟内存使用free

(4)使用free释放动态内存的一部分

(5)对于同一块动态内存多次释放

(6)动态开辟内存忘记释放

5、柔性数组


1、为什么要有动态内存管理?

首先我们了解一下**什么是动态内存管理。简单来说就是C语言支持你自己去开辟和释放内存。**比方说我想要40字节的内存,那么我们就可以灵活的开辟这块内存,然后再在这块内存中存放数据。

那么我们直接用常规的方式存储数据不就好了吗?可以是可以,但是有两个缺点:

1、空间开辟大小是固定的:比方说int他就是4个字节,没法改变。

2、数组在声明的时候,必须指定数组的长度。数组空间大小确定了就不能调整了。

而动态内存管理的出现,就可以让程序员自己去开辟和释放内存,变得更灵活了。


2、malloc和free

(1)malloc函数

malloc是一个动态内存开辟函数,其函数原型如下:

这个函数就是用来开辟空间的,其中malloc开辟的空间单位为字节。

如果开辟成功,就返回一个指向这块空间的指针。

如果开辟失败,就返回一个空指针。而null无法被使用。

这就意味着,我们在开辟内存后一定要检查返回的是否为空指针。

如果开辟成功,返回的指针类型为void*,所以我们在使用时要先强制转化该指针。

例如,我们现在开辟一块空间并使用它(放入内容):

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);
	if (p != NULL)
	{
		*p = 5;
		printf("%d", *p);
	}
	free(p);
	p = NULL;
	return 0;
}

以上是一套完整的开辟空间以及使用代码。大家应该都发现了里面出现了free,那么什么是free呢?


(2)free函数

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

其函数原型如下:

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

如果参数ptr指向空指针,那么函数什么事都不做,

malloc和free函数的声明都在stdlib.h头文件中。

注意:函数在申请内存之后一定要释放内存,否则可能会导致内存泄漏,或者由于重复开辟内存空间导致占用大量内存!


3、calloc和realloc

(1)calloc函数

函数原型:

它可以为num个占用size个字节的元素开辟空间。

和malloc的区别是,calloc会把申请的空间全都初始化为0。

例如:

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)calloc(5,4);
	if (p != NULL)
	{
		int i = 0;
		for (i = 0;i < 5;i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);
	p = NULL;
	return 0;
}

运行结果:


(2)realloc函数

这个函数可以让我们调整内存。

函数原型:

我们先给定一个申请好动态内存的地址,在传入想到让其扩大后的最终大小。

返回值为原内存起始位置。

但需要注意的是,倘若我们无法调整到想要的空间大小,也就是说在原地址的基础上我们没有足够大的空间来扩充,那么该函数会找一个新的地址去开辟这么大的空间,在返回新开辟的位置的起始地址。

验证:

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(1);//开辟1个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, 2);//扩为两个字节
		printf("%p\n", p);
	}
	else
		return 1;
	free(p);
	p = NULL;
	return 0;
}

输出结果:

测试代码2:

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);//开辟4个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, 16);//扩为16个字节
		printf("%p\n", p);
	}
	else
		return 1;
	free(p);
	p = NULL;
	return 0;
}

运行结果:

那么有没有一种可能就是说他在内存中根本找不到这么大的位置来放这块内存呢?

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);//开辟4个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, INT_MAX);//扩为最大整形数字
		printf("%p\n", p);
	}
	else
		return 1;//如果p为NULL直接结束程序。
	free(p);
	p = NULL;
	return 0;
}

运行结果(调整失败):
所以说,在我们使用realloc是要注意一些。


4、常见的动态内存的错误

(1)对空指针的解引用问题

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(INT_MAX);//开辟4个字节
	*p = 5;
	free(p);
	p = NULL;
	return 0;
}

我们无法开辟这个大小的内存空间,导致开辟失败返回空指针。程序直接挂掉


(2)对动态开辟空间的越界访问

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(5*sizeof(int));
	int i = 0;
	for (i = 0;i < 6;i++)
	{
		*(p + i) = 1;
		printf("%d", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

当i=5时越界访问,程序直接挂掉。


(3)对非动态开辟内存使用free

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = 5;
	printf("%d", *p);
	free(p);
	p = NULL;
	return 0;
}

没有输出结果。


(4)使用free释放动态内存的一部分

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(5*sizeof(int));
	p++;
	free(p);
	p = NULL;
	return 0;
}

程序直接挂掉。


(5)对于同一块动态内存多次释放

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(5*sizeof(int));
	p++;
	free(p);
	free(p);
	p = NULL;
	return 0;
}

程序直接挂掉。


(6)动态开辟内存忘记释放

注意,这种情况可能会造成内存泄漏问题,当程序简单一些时,有可能可以正常运行,但是这不代表着可以不释放内存!开辟内存和释放内存一定是成套使用的。


5、柔性数组

柔性数组就是一种可以让我们灵活调整其空间的数组。

但是需要注意:

1、结构体中柔性数组成员前面至少包含一个其他成员

2、sizeof返回这种结构的大小时不包含柔性数组

3、用malloc调整结构中柔性数组的大小时,调整的最终大小应该大于这个结构体的大小。

现在我们使用以下柔性数组:

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
struct s1
{
	int a;
	int arr[0];//柔性数组我们不填入他的大小
}s;
int main()
{
	struct s1 *p = (struct s1*)malloc(sizeof(struct s1) + 10 * sizeof(int));//结构体指针名称为p
	//开辟一块地址,大小为结构s所占字节加上100个整型大小(给柔性数组)
	if (p != NULL)//还记得吗?我们要判断是不是空指针
	{
		int i = 0;
		for (i = 0;i < 10;i++)
		{
			p->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素
			printf("%d ", p->arr[i]);
		}
		struct s1* p1 = (struct s1*)realloc(p, sizeof(struct s1) + 15 * sizeof(int));
		//判断是否为空指针(我们这里省略了)
		for (i = 10;i < 15;i++)
		{
			p1->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素
			printf("%d ", p1->arr[i]);
		}
		free(p);
		p = NULL;
		return 0;
	}
	else
		return 1;
}

这是一套完整的使用并调整柔性数组大小的代码。


那么我们现在思考一下,如果不用柔性数组,我们可以完成以上操作吗?

cpp 复制代码
#include <stdio.h>
#include<stdlib.h>
struct s1
{
	int a;
	int *arr;
}s;
int main()
{
	struct s1 *p = (struct s1*)malloc(100 *sizeof(int));
	//这里我们先划定p这个结构体指针变量所在的那一块空间
	if (p != NULL)//还记得吗?我们要判断是不是空指针
	{
		p->arr = (int*)malloc(10 * sizeof(int));
		int i = 0;
		for (i = 0;i < 10;i++)
		{
			p->arr[i] = i;//下标引用操作符的实质是指针
			printf("%d ", p->arr[i]);
		}
		p->arr = (int*)realloc(p,15 * sizeof(int));//调整内存空间
		for (i = 10;i < 15;i++)
		{
			p->arr[i] = 1;//下标引用操作符的实质是指针
			printf("%d ", p->arr[i]);
		}
		free(p->arr);//我们开辟了两次内存,所以就要释放两次。
		p->arr = NULL;
		free(p);
		p = NULL;
		return 0;
	}
	else
		return 1;
}

尽管过程有些许的坎坷,但我们也是完成了这串代码。

以上两串代码的输出结果:

那么这两种代码写法哪一个更好的?

答案是柔性数组更好。**因为柔性数组方便内存释放,**我们只用释放一次就好。

其次,**柔性数组更有利于访问速度,**而且我们的代码写起来不也更简单吗?

好了,今天的内容就分享到这,觉得有帮助的老铁点点关注支持一下,我们下次再见!

相关推荐
XH华3 小时前
初识C语言之二维数组(下)
c语言·算法
Uu_05kkq6 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普9 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A9 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
1 9 J10 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
仍然探索未知中11 小时前
C语言经典100例
c语言
爱吃西瓜的小菜鸡11 小时前
【C语言】矩阵乘法
c语言·学习·算法
Stark、13 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
deja vu水中芭蕾13 小时前
嵌入式C面试
c语言·开发语言
stm 学习ing15 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl