动态内存管理

前言

在前面的静态通讯录这篇博客中,发现我们所写的数组大小一旦确定好,就会向内存空间申请一块固定的连续空间,当我们使用的时候可能会出现内存浪费和内存不够用的情况。
而今天介绍的动态内存管理可以在一定程度上解决这个问题。

动态内存

内存区一般最常见的是这三个
在这里插入图片描述
栈区(Stack):编译系统自动分配释放,主要存放 函数参数,局部变量 等 .
堆区(heap):由程序员分配释放管理,一般由 malloc,new等内部存储函数使用, 如果没收回,程序结束时由操作系统收回。创建堆时,一般在堆的头部 用一个字节存放堆的大小;回收堆时,通过查看这个 字节的内容,可得知需要释放的多大的内存。
静态区:存放 全局变量 和 静态变量 ,程序结束时由系统释放,分为全局初始化区和全局未初始化区。
常量区:存放 常量 ,程序结束时由系统释放 。
程序代码区(上面4个区统称数据区):存放运行 或准备运行的程序代码,由系统调度

动态内存函数

malloc函数

void* malloc (size_t size)

该函数向堆区申请了一块连续的空间,同时返回这块空间的指针
注意事项:

如果开辟成功,则返回一个指向开辟好空间的void类型指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* tmp = (int*)malloc(40);		//用malloc申请40个字节,用来存放10个整形
	if (tmp == NULL)					//判断malloc是否申请成功
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	for (int i = 0; i < 10; i++)		//malloc申请的是一块连续的空间,可以当作数组来使用
	{
		tmp[i] = i + 1;					//存放1~10
		printf("%d ", tmp[i]);			//存放之后直接打印
	}
	return 0;
}

free函数

void free(void* ptr);

注意事项:
1.如果 ptr指针所指向的空间不是动态内存开辟的,那么free函数的作用是未定义的,很可能会导致程序崩溃
2.如果 ptr是NULL,那么free函数将什么都不会做

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* tmp = (int*)malloc(40);		//用malloc申请40个字节,用来存放10个整形
	if (tmp == NULL)					//判断malloc是否申请成功
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	for (int i = 0; i < 10; i++)		//malloc申请的是一块连续的空间,可以当作数组来使用
	{
		tmp[i] = i + 1;					//存放1~10
		printf("%d ", tmp[i]);			//存放之后直接打印
	}
	free(tmp);
	tmp=NULL;
	return 0;
}

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

calloc函数

void* calloc(size_t num,size_t size);

向内存申请一块 num个 size大小的内存空间,并把空间内每个字节初始化为0
注意事项:

如果开辟成功,则返回一个指向开辟好空间的void类型指针。
如果开辟失败,则返回一个NULL指针,因此calloc的返回值一定要做检查。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* tmp = (int*)calloc(10,sizeof(int));		//向内存申请10个 int类型大小的空间
	if (tmp == NULL)								//判断是否判断成功
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", tmp[i]);						//打印
	}
	free(tmp);
	tmp = NULL;
	return 0;
}

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

realloc函数

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

realloc 函数的出现,使得动态内存管理更加的灵活。例如有些时侯我们觉得前面申请的空间太小了不够用,或者我们会觉得申请的空间过大了太浪费,这个时候我们就可以通过使用 realloc 函数对之前开辟的动态内存空间的大小再次进行合理的调整

  • ptr指针是指向要调整的内存起始地址
  • size:调整之后的总字节大小
  • 返回值是指向 调整之后空间的起始地址
  • 调整失败,返回空指针,所以这里也需要判断
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* tmp = (int*)calloc(10,sizeof(int));
	if (tmp == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	int* p = (int*)realloc(tmp, 20 * sizeof(int));		//调整空间大小
	if (p == NULL)										//判断调整是否成功
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	tmp = p;
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", tmp[i]);
	}
	free(tmp);
	tmp = NULL;
	return 0;
}
 

运行结果:
在这里插入图片描述
如果扩容成功,也分两种扩容情况:当前空间与后相邻空间之间的空间是否足够 realloc 函数进行扩容操作。

扩容情况一
  若空间足够,则直接执行扩容操作,并在扩容完成后返回指向起始位置的指针。
在这里插入图片描述
扩容情况二
  若后续空间不够,则将会在堆区中重新寻找合适的空间(足以容纳下扩容后的全部空间),并将原空间内的数据全部拷贝过来,接着释放原空间,并在扩容完成后返回指向新空间起始位置的指针
在这里插入图片描述

常见的动态内存错误

1.判断是否成功开辟

列:

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + 1) = 0;
	}
	free(p);
	p=NULL;
	return 0;
}

上面代码中,在 malloc 执行后,没有对 p 指针进行检查,因为 malloc 也可能失败,失败时会返回一个空指针,如果是这样,那下面就是对空指针进行解引用操作,这样是不合适的。

2.对动态内存的越界访问

int main()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问,会导致程序挂掉
	}
	free(p);
	p = NULL;
}

3.对非动态内存的空间使用free

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

需要注意free针对的是堆区上的空间,而上述代码中的 p 指针指向一个整型变量,整型变量是在栈区申请的空间

4.使用 free 函数释放动态内存空间的一部分(free释放时要从头释放):

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+1;
		p++;
	}
	free(p);
	p=NULL;
}

上面代码中的 p 指针,在经历过多次 p++ 之后就不再指向这个整型的起始地址了,此时再去 free 就会出问题。
   避免此类错误的方法是,在使用指针前保存好初始指向,并在进行动态内存释放时释放完整的动态内存空间。

5.对同一块动态内存空间多次释放(一定要记得在释放空间后把指针置空):

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
 
	free(p);
	//...(在中间又进行了很多其它操作之后,忘记了已经释放过动态内存空间,并进行了重复释放)
	free(p);
	//重复释放动态内存空间
	p = NULL;
 
	return 0;
	}

如上面代码,在程序的最后对 p 指针 free 了两次,此时程序就会报错。本质原因就是对同一块动态内存多次释放程序会报错。但是如果在第一个 free 的后面把 p 指针赋为空指针,就算再 free就没有任何问题了。

6.不释放动态内存空间(会导致内存泄漏):


void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p != NULL)
	{
		*p = 10;
		//判断非空后进行使用
	}
	//使用后没有释放动态内存空间,在程序终止前该动态内存空间都不会被释放,将会占用计算机系统的内存
}
 
int main()
{
	test();
	while (1);
	//为了演示内存泄漏,使程序不终止
	return 0;
}

避免此类问题的方法是,是使用一个释放一个

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值