最近在巩固算法,这个先做笔记了,有空再精细研究。
知道可以动态申请堆空间,堆空间是系统维护的内存块链表,用户申请时,系统找到合适大小的堆给用户,系统还会把剩下的空间再切割备用,所以会出碎片。
blabla。。。。。。
一直以来困惑一个问题。系统怎么知道那一块大小,送一个指针过去它就释放和malloc对应的空间?大小什么的都记录在哪?
看了一个自实现,想明白一些了:
struct mem_control_block { int is_available; int size; };这些内存块,是有内存控制块的,是否可用,应该就是分配和释放时候用的。分配的时候,查查是否可用,分配成功之后改成不可用,释放的时候,把标记再改为不可用。另外一个size就是分配空间的大小了,这样的话释放空间的时候就知道释放多大了,相应的,这个也应该是分配的时候赋值的,因为用户指定大小。
然后就困惑一个问题,怎么映射?这个结构体,怎么映射到对应空间上?没个指针什么的?
第一反应是协议包的封装,我们封装协议包时,本就是一块连续空间,把包头放在前边,包的内容放在后边而已。其实仔细想想,协议包又何尝不是叫做协议控制块呢,和内存控制块有什么区别么?
所以这样就说通了,真实的内存块可能是如下模样:
+----------+---------------------------------------------------+
| MCB | MEM_FOR_USER |
+----------+---------------------------------------------------+
^ ^
| |
sys user
有系统和用户两个指针,给用户第二个指针,就隐藏了内部实现了,然后系统指针在前边就能看到内存控制块。
你猜怎着?下文真是这样说的,这样你free()回传一个指针的时候,我系统直接回滚一个控制块的大小就好了。所以学东西要多思考,多疑问,你想的,可能就是正确答案,这样还能加深印象。
不过,问题也来了,如果真是按这种结构来存,那如果用户自己乱改指针,改到前边的MCB,不就糟了么?也就是假设这个MCB并不是对用户不可见的,只是没给用户直接访问途径。这样一来,一个误操作就能导致最后内存的释放不正确!可能释放少,也可能释放多。前者可能会导致内存”丢失“?,后者可能会导致地址”重合“?这会不会出什么大乱子?!系统有没有什么容错机制?(其实这篇帖子就是自己实现malloc的,看看他有什么高招)
先做个基于原生malloc()的破坏性试验试试?没想好怎么测试,因为释放的内存也不是把内容全都置零,指针传一次给free()再传一次也不会提示错,所以怎么验证释放成功与失败也是个问题。
或者强行提取内存内容,看看有没有数值和申请的内存大小一致。
void*指针不让提取,用个int*指针试试看
上图可以看到,往前退一个int地址(4字节)后,打印出了105(再往前或者往后都是0),不知是不是巧合100+5?,100个地址给用户,5个地址存状态?当然,要指出一点,上文给出的实现,其实浪费空间了,想知道一个内存块是否分配,是不需要一个int型的,一个char型足矣,一个char型加一个int型的话,就刚好是5地址。
malloc()换个大小的再试试:
分配50大小,打印出的前两格的值是57,多7个,所以上边的假设也不成立。
分配10大小,打印17
分配200,打印209
毫无规律,当然也不排除提取地址不对的可能性,但是整体数量级差距不大,只是具体数值是多几的问题上,所以更像是细节上的问题。
另外一个问题也先提出来吧,对这个疑问可能有些帮助,分配给你的空间,一定是准确的你指定的大小么?多余的都完整截去当碎片重新分配了?或者分配出来本来就有零头?
找到这样一句话(但这说的是sbrk(),而自己的实现没处理,不确定原生malloc()也是这样不做处理。):
- 由于
sbrk()
可能会交回比我们请求的更多的内存,所以在堆(heap)的末端 会遗漏一些内存。
另外,free()操作时:
传array释放之后,指向前4字节位置的ptr的内容也从近似内存片段大小片成一个野数,比如135169。
前8字节位置的0还是0,没变化。
另外一个问题就是:所谓系统维护一个”链表“,通过链表来找到一个又一个的内存块,那这个链表什么样?其实应该一个非常简单的链表就好了,因为所需的信息被储存在内存块的前半部分了,不用在链表结构多下工夫。
anyway,不在这个细节上浪费时间了,再看看那个malloc实现。
=========================================================================================================================
所以,这就是自实现的free()函数
void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; }回退一个结构体,赋值指针,然后给成员is_available设置成可用,这里并没有去管那个size,和上边推测的free()有出入,前提是上边的推测是正确的,不过也可能实现不太一样,位置不一样(不过再不一样也不能那么巧合的只在数值差几了~)。
malloc的实现例子(缺点多多,只是一个参考):
void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; }这是原文:https://www.ibm.com/developerworks/cn/linux/l-memory/index.html
小结:
这些东西各有优缺点,是根据使用场景进行的优化选择,有Doug Lea Malloc、BSD Malloc与Hoard等。
直接重写malloc(),也算是一种实现内存管理的方式。
malloc()这种形式管理内存会遇到很多麻烦,比如到底是申请者还是使用者来释放,生存期太长,控制不好会不会爆。
===========================================================================================================
内存池:
内存池可以分阶段划分,多个阶段用多个不同的池子,过了阶段直接抹掉。还可以注册清除函数来完成析构。
后边的眼晕,先不看了
另外,看一个例子,malloc8个字节,使用时候不小心超了,然后再free就会错。
http://www.cnblogs.com/chuyuhuashi/p/4480117.html