学习笔记1

本文探讨了动态内存分配中可能出现的内存分配失败问题及其解决方案,包括递归分配和循环分配策略。同时,讨论了如何确保使用动态内存的安全性,提出通过二级指针进行初始化检查。最后,文章涉及内存生命周期的管理,包括使用指针数组和链表来跟踪内存块,并提出了基于引用计数和定时清理的内存管理策略,以防止内存泄漏和碎片问题。
摘要由CSDN通过智能技术生成

内存分配失败问题

在动态内存分配中,内存分配失败是有可能的。而通常的解决办法是if(pointer != null),避免其他函数访问空指针。不过,若是内存分配失败,如何处理。而若在设计程序时,分配的这块内存又是整个应用程序必须的,那么我们就不得不考虑内存分配失败后,程序如何处理。请看下面代码:

int main()
{
	int* array = (int*)malloc(1024*sizeof(int));
	if(array != nullptr)
		//code...
 	free(array);
	return 0;
}

在分配int数组时,有可能失败。而在失败后,对于变量array的操作都将会无效。也就是说,对于上面代码来说,一旦内存分配失败,那么整个进程直接结束。为了避免类似状况发生,就需要考虑内存分配失败后的处理。首先,能够想到的方法是再次分配,那么以上述代码,下述代码为:

int* create(size)
{
	int* array = (int*)malloc(size*sizeof(int));
	if(array == nullptr);
		create(size);
	return array;
}

这段代码是可以解决问题,不过递归函数一旦陷入死循环会消耗大量内存,因此修改代码:

int* create(int size)
{
	int* array = (int*)malloc(size*sizeof(int));
	
	return array;
}
int* create_(int size)
{
	int* array = nullptr;
	while(array == nullptr)
	{
		array = create(size);
	}
	return array;
}	

这段代码执行时间不确定,需要修改代码:

int count = 0;
int all = 0;
int* create_(int size,int running)
{
	int* array = nullptr;
	int c = 0;
	while((array == nullptr) && (running>0))
	{
		array = create(size);
		++c;
		--running;
	}
	count = c;
	all += c;
	return array;
}

再次分配方法就笔者目前的知识水平想到的也只有这里了。另外,笔者给出另外一条思路,那就是判断程序所申请的连续内存空间有没有,没有就发送请求,然后线程阻塞等内存空间准备好。这个思路涉及到线程编程,就不给出代码了。

使用动态分配的内存问题

对于使用动态内存分配的函数,为了避免访问到空指针,同样有if(pointer != nullptr) 。只是这样的方法代码量显然增加,即每传入的指针都要保证不能是空指针访问。那么,能不能只做一次判断是不是指向空指针,而多次使用呢。

我们首先所能想到的是,指向指针的指针,即二级指针:typename**。看下面代码:

void fun(char* data)
{
	std::cout<<data<<'\0';
}
int main()
{
	char a = 'a';
	fun(&a);
	return 0;
}

由地址运算符&可以保证传入指针不是空指针,因此我们只需要保证指针所指向的内容不为空。接上一段例子,动态分配初始化函数实现:

void init(int** array,int lenght);
{
	for(int index = 0;index < lenght;++index)
		*array[index] = 0;
}

这个函数将访问检查交给了调用函数,即是说,在调用函数中:

void fun(int* s)
{
	if(s != nullptr)
		int* p = s;
		init(&p);
}

类似的,查询,添加,删除,排序,复制等等数据操作函数都可以这样实现。

生命周期问题

动态分配内存由于是用户自己创建,也由用户自己释放,所以不可避免需要管理每一块动态分配的内存。而在程序中,很容易出现重复释放,尝试访问已释放内存空间(主要是全局指针)。因此,需要将申请的每一块内存都集中管理。

要实现管理,需要将申请的每块内存的指针保存下来:

typename* name[];

这是采用指针数组保存,我们需要两个指针数组用以保存还在使用,以及已释放内存的指针:

typename* usingPointer[];
typename* usedPointer[];

其次,已经使用过的内存块不着急释放,这是为了程序再次申请时,恰巧内存块在已使用内存块大小之内,就可以从used 转入using,再使用指针。不过,针对这个功能,采用数组的方式不合适了,需要链表:


struct list
{
	int flog;
	struct list* next;
	typename* memory;
}*usingM,usedM;

这里有个问题,那就是这种方法极易产生内存碎片,且需考虑内存不足问题。因此,需要对内存进行释放。有3个步骤,其一,最大内存释放,即定义一个程序总内存申请最大数,using+used;其二,引用计数,设置各块内存引用计数,分配内存;其三,引入定时触发器,每到一定时间进行内存清除。综上所述,其结构为:

unsigned long long time;//计时
int max;
struct Memory
{
	struct list* next;
	int sign;//是否正在使用
	enum type;//类型
	int count;//引用计数,用于函数主动释放
	void* memory;
};

这里,有个问题,程序没有主动释放,就会造成内存难以申请,所以需要添加代码:

class M
{
public:
	struct list* l;
	M();
	~M();
}

通过类的构造与构析函数自动修改引用计数与标志,其中,构造函数修改标识与计数,构析函数修改标志为used,而当每次进行内存清理时,将used标志的引用计数减一。这样,内存块的生命周期就出来了,同时,因为局部作用域的特性,可以使内存块自动转入used,接着再根据引用计数,与基础生命周期,可以灵活设置内存块的生命周期:

class M
{
public:
	static int baseCount;//通过基础生命周期,可以延长内存块的使用时间
	struct list* l;
	M();
	~M();
}

此外,若要实现类似java的自动回收机制,可以小小改变一下,M类构造、构析函数只改变标志位,而每次清理内存时将所有内存块的计数加一,接着根据划分的生命周期对used进行清理。根据静态变量的特性,M类在整个程序结束之前是不会调用构析函数,因此M类可以兼顾全局使用的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不过日落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值