动态内存管理

 "痛苦难以避免,而磨难可以选择。"-->村上春树

作者:Mylvzi 

 文章主要内容:动态内存管理 

动态内存管理_第1张图片

前言:为什么要进行动态内存管理-->之前开辟内存的方式对于内存的使用过于局限;

动态内存管理_第2张图片

 一.动态内存函数的介绍

所有的与动态内存分配有关的函数都包含于这个头文件之中

1.malloc函数-->动态内存开辟函数

动态内存管理_第3张图片

作用:在堆区之中申请size字节大小的空间,并返回开辟内存空间的起始地址;

注意:

1.在申请内存的过程中有可能申请失败,如果申请失败,会返回NULL;(一定要进行判断,对空指针的解引用是非法的)

2.malloc的返回值是void*,使用者可以根据自身需求进行强制类型转换;

3.动态开辟的内存并不会自动销毁(还给操作系统),销毁有两种方式,使用free函数释放内存空间,程序退出;

 2.free函数-->动态内存释放函数

动态内存管理_第4张图片

作用:释放之前已经被动态内存函数(malloc,calloc,realoc)开辟的内存空间,将内存空间还给操作系统 

注意:

1.只能释放动态开辟的内存,不能释放在栈区开辟的内存:

2.释放之后,要及时对释放空间的起始地址置于NULL,避免野指针的出现

代码示例: 

int main()
{
	int* p = (int*)malloc(40);//在堆区申请40byte空间
	//判断malloc函数是否成功在堆区申请到空间,失败,返回NULL  
	//成功,返回所申请空间的初始地址
	if (p == NULL)
	{
		perror("malloc");//打印错误信息
		return 1;
	}
	
	//使用完毕,使用free函数对内存空间进行释放(和栈区内存的申请是不同的)
	free(p);
	p = NULL;//及时将释放空间的起始地址置于空指针

	/*free函数只能释放动态开辟的内存空间*/
	//int a = 10;
	//int* ptr = &a;
	//free(ptr);//err
	return 0;
}

3.calloc函数-->作用和malloc函数相同,进行动态内存开辟

动态内存管理_第5张图片

作用:向内存申请num个size字节大小的内存空间,并把内存空间的内容初始化为0 (malloc函数并不会进行初始化)

 代码示例:

//calloc函数-->void* calloc(size_t num,size_t size)
int main()
{
	//int arr[10];
	int* p = (int*)calloc(10, 4);
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	//打印数据
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//打印10个0
	}

	//使用完毕,释放内存空间
	free(p);
	p = NULL;

	return 0;
}

4.realloc函数-->调整动态内存大小的函数(重要,实现动态内存分配的核心函数)

 动态内存管理_第6张图片

作用:将ptr所指向的内存空间的大小改为size字节(增容) 

但是,堆区的内存空间是有限的,在重新分配内存大小时,可能会出现内存空间不足的情况,导致返回值有三种情况;

1.返回旧空间的起始地址

2.返回新开辟空间的起始地址

3.返回NULL

当返回值不确定时,重新创建一个变量来接受

动态内存管理_第7张图片

 

代码示例:

//realloc函数
//void* realloc(void* ptr ,size_t size)
int main()
{
	//动态开辟一块内存空间
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	//赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}

	//增容
	int* ptr = (int*)realloc(p, 80);//增容为80byte
	if (ptr != NULL)//只要不是NULL,就证明开辟成功
	{
		p = ptr;
		ptr = NULL;
	}
	else
	{
		perror("realloc");
		return 1;
	}

	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}

	//使用完毕
	free(p);
	p = NULL;

	return 0;
}

二.动态内存常见错误 

1.对NULL的解引用操作(要检测返回值是否为NULL, 是NULL应该直接退出函数)

	int* p = (int*)malloc(40);
	*p = 20;//未检测返回值是否为NULL
	free(p);
	p = NULL;

2.对动态开辟空间的越界访问(不能超过你申请内存空间的大小) 


	int* p = (int*)malloc(20);//申请了一个能容纳5个int类型数据的空间
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;//超过内存限制
	}

3. 对非动态开辟内存进行free释放(free只能释放动态开辟的内存)


	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;

4.使用free释放一块动态开辟内存的一部分 (不能占着茅坑不拉屎)

	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p++;
	free(p);//ERR p此时已经不再是起始位置,不能释放内存的一部分
	p = NULL;

5.对同一块动态内存多次释放 

	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	free(p);//err  已经释放过的不能再被释放

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

	void test()
	{
		int* p = (int*)malloc(100);
		if (NULL != p)
		{
			*p = 20;
		}
	}
	int main()
	{
		test();
		while (1);
	}

总结:动态内存的开辟虽然方便,但它是把双刃剑,会带来其他危险,在使用过程中要注意规范,养成以下习惯可以规避一些风险

1.在使用动态内存函数开辟完空间后,及时判断是否申请成功(p是否为NULL) 

2.要及时释放动态开辟的内存(谨记malloc和free是成对出现的),并在释放后将地址置于NULL,避免野指针的出现;

3.在接受realloc函数的返回值时,最好创建一个新的变量来接收,并判断是否增容成功

补充一点

        free函数的参数中只有一个void* 类型的地址,并未告诉你对应空间的大小,那他是如何精准释放空间呢?原因在于,在malloc函数分配内存时,系统会在你所申请的内存之前(或之后,具体看编译环境)设置一个“头部信息”,free函数在得到起始地址后,会根据头部信息的内容知道内存的具体大小,从而实现对内存的精准释放

三.动态内存分配笔试题讲解 

1.题目一:

动态内存管理_第8张图片

解决方案:你想改变实参str的值,为实参开辟空间,改变实参,要传递实参的地址! 

void GetMemory(char** p)//使用二级指针存放str的地址
{
	*p = (char*)malloc(100);//对p解引用,得到str所在的空间
}                           //为str分配足够的空间
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);//输出hello world
}

2.题目二:

动态内存管理_第9张图片 

 注意:返回栈区空间地址,产生野指针,非法访问内存

3.题目三: 

动态内存管理_第10张图片

free之后一定要及时将旧地址置为空指针 

四.c/c++程序内存分配

动态内存管理_第11张图片 

五.总结 

 总结:动态内存分配是一种管理内存的重要方式,要了解与动态内存管理有关的函数(malloc,calloc,realloc,free),熟记动态内存分配过程中的危险(是否分配成功,realloc函数的返回值有三种情况),了解基本的内存分配知识;感谢大家观看

你可能感兴趣的:(java,算法,开发语言)