内存管理:动态内存管理(main)、静态内存管理、自动内存分配

内存管理:动态内存管理(main)、静态内存管理、自动内存分配

一、基础概念

1、什么是动态内存管理

动态内存管理也叫动态内存开辟。指在程序运行时,根据需要动态地分配和释放内存空间的过程。它允许程序在运行时根据实际情况来动态地请求分配内存,以满足不同大小和数量的数据存储需求。

2.动态内存管理设计操作

1) 内存分配:当程序需要更多的内存空间来存储数据时,它可以通过动态内存分配来请求一块合适大小的内存空间。malloc、calloc...

2) 内存释放:当不再需要某个内存空间时,程序可以通过动态内存释放将该空间归还给系统,以便其他部分可以使用。free

3.常见的动态内存管理方法

1 ) 堆分配:在堆中分配内存空间,可以使用诸如malloc、calloc、realloc等函数进行分配,并使用free函数进行释放。

2 ) 操作系统提供的动态内存管理:操作系统也提供了一些机制来管理进程的动态内存,例如Unix/Linux系统中的brk和sbrk系统调用,以及Windows系统中的HeapAlloc和HeapFree函数。

二、自动内存分配和静态内存管理

1.自动内存分配

栈区申请空间(函数中的普通变量)
当函数被调用时,会在栈上分配一定大小的内存空间来存储函数内的局部变量和一些临时数据。这些变量的内存空间在函数执行完毕后会被自动释放,因此称为自动变量

#include
int main()
{
	int a = 10;//4个字节
	int arr[10] = {};//40个字节
	return 0}

2.静态内存管理

静态存储区
静态内存分配是通过定义全局变量、静态变量或者常量来实现的

int global_variable; // 全局变量,存储在静态存储区
static int static_variable; // 静态变量,存储在静态存储区
const int constant_value = 10; // 常量,存储在静态存储区

三、常见的动态内存管理函数

堆区申请空间(动态申请的内存)

1.malloc

malloc函数接收的参数:

  • 所需的内存字节数

malloc的特点:

  • malloc会找到合适的空闲内存块,并且这样的内存是匿名的,换句话说说malloc分配内存,但不会为其命名
  • malloc返回动态分配内存块的首字节地址,因此我们可以把该地址赋给一个指针变量,并用指针来访问内存
    内存管理:动态内存管理(main)、静态内存管理、自动内存分配_第1张图片
#include
#include//errno
#include//strerro
#include//malloc

int main()
{
	int* p = (int*)malloc(40);							//malloc是(void*)类型,这里强制转换成(int*)
	//进行检查:
	if (p == NULL)										
	{
		printf("%s\n", strerror(errno));
		return 1;										//异常返回 
	}
	
	//使用内存
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//输出检查试试
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//程序退出系统会自动回收空间,回收用free(),防止内存泄漏
	free(p);											//先释放,free必须释放动态内存开辟的空间
	p = NULL;											//在赋值
		return 0;										//正常返回
}

2.free

为什么需要free( )进行释放:

自动变量使用的内存数量在程序执行期间自动增加或减少,但是动态分配的内存数量只会增加,除非用free ( )进行释放 ;

关于内存泄露(memory leak):

假设我们的程序需要一直运行(循环)且我们忘记对该程序进行内存释放,那么我们的程序就不停的创建内存块,并且当函数(开辟内存)结束后,该内存块也无法再被使用和被访问,如此循环往复,我们有限的内存资源终将会被消耗殆尽;
实际上,也许在循环还没结束前就已经耗尽所有内存。这类问题被称为内存泄漏

内存管理:动态内存管理(main)、静态内存管理、自动内存分配_第2张图片

3.calloc

calloc接受两个无符号整数作为参数:

  • 所需的存储单元数量
  • 存储单元的大小(以字节为单位)

calloc的特点:

  • 它把块中所有位都设为0

内存管理:动态内存管理(main)、静态内存管理、自动内存分配_第3张图片

#include
#include//errno
#include//strerro
#include//calloc


int main()
{

	//calloc(size_t num, size_t size) 且数组所有值初始化为0
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	//使用内存
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;

	return 0;	
}

4.realloc

realloc的出现意义:

  • 使动态内存管理更加灵活,realloc函数可以做到对动态开辟内存的大小进行调整

realloc追加的两种情况:

  • 追加后内存足够 ->直接加
  • 追加后内存不够 ->重新一次性开辟(之前占有的内存 + 新增开辟的内存),并且realloc自动将之前占用的内存空间销毁

过多使用realloc:

  • 会导致内存的碎片化
  • 内存利用率不高,效率下降(申请和释放的频率上升)

realloc也可等价为malloc:

realloc(NULL, 40);//传递空指针
malloc(40);

内存管理:动态内存管理(main)、静态内存管理、自动内存分配_第4张图片

#include
#include//errno
#include//strerro
#include//realloc


int main()
{
	//realloc 合理调整,使用内存
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	//使用:1~10
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//扩容,直接将ptr赋给p是危险的(扩容内存不足)
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL)
	{
		p = ptr;
	}
	//输出查看数值,发现原来开辟赋值的数并没有改变
	for (int i = o; i < 10; i++)
	{
		printf("%d", *(p + i));
	}
	free(p);
	p = NULL;

	return 0;
}

四、常见错误

Debug Assertion Failed

1.对NULL指针的解引用操作

没有判断指针是否为NULL就直接赋值

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

访问的内存大于开辟的内存造成了越界访问

3.对非动态开辟内存的free释放

int a = 10;
free(a);

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

改变了指针初始所指向的位置,想要释放该指针时无法释放掉全部所开辟的空间

5.对同一块空间多次释放

多次用free释放同一个指针
第一次释放的时候原指针已经变成了野指针
(可以在第一次释放后给该指针赋值NULL)

6.动态内存开辟忘记释放

内存泄漏
忘记free()或者未执行free()

五、错误情况(实例)

调用函数指针已经被释放

//错误代码
void GetMemory(char* p)
{
	p = (char*)malloc(100);				//函数调用结束 p 销毁
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "xiangshuidajiao");      //此时 str = NULL 空指针的解引用
	printf(str);
}
int main()
{
	Test();  							 //无法正确打印

	return 0;
}

过程分析:
调用Test ( ) -> 给指针 str 赋为 NULL -> 调用 GetMemory ( ) -> 给指针 p 开辟了 100 字节空间 但是调用结束p销毁,p的空间还给操作系统,但malloc的空间还存在且没有程序free其开辟空间(造成内存泄漏)-> 回到str str依然为空指针 -> 字符串函数解引用崩溃

正确修改的方法之一:

//正确代码
void GetMemory(char** p)
{
	*p = (char*)malloc(100);					//*p=str
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);							//str存放动态开辟的100字节的地址
	strcpy(str, "xiangshuidajiao");
	printf(str);
	//释放
	free(str);
	str = NULL;
}
int main()
{
	Test();

	return 0;
}

[1]以上图片来源于:cplusplus官网
[2]参考视频:b站鹏哥
[3]参考书籍:C Primer Plush
[4]关于C语言内存五大区文章推荐:https://blog.csdn.net/m0_73381672/article/details/131726780
[5]关于字符串函数:https://blog.csdn.net/ggh_567/article/details/134609732

你可能感兴趣的:(c语言)