malloc申请堆内存
1.malloc申请的空间,是否记录空间⼤⼩?若是记录,那所记录的空间⼤⼩在哪呢?为什么利⽤free 释放不需要空间⼤⼩参数呢?
malloc申请空间时,记录其空间⼤⼩(其空间上⽅,有⼀个数据头,头部信息就记录了申请空间的⼤⼩),当调⽤free函数时,即需要读取头部信息得到需要释放的字节数
malloc申请的空间,⼀ 申请的内存 ⼩的靠近数据区 ,⼤的靠近栈区,⽽中间的是“⽆⼈区””
malloc⼯作在user space(⽤户态),是以什么样的数据结构组织从内核中申请分配⼀⼤块内存?
内核态(资源都是由OS管理的)和⽤户态交互(调⽤系统API open、close、read、write )
malloc第⼀次向内核申请(申请堆内存的系统API)brk()(增加或减⼩brk指针相当于申请和回退申请的内存)和mmap()(将磁盘上的页⾯加载到虚拟地址空间上) 申请的单位是页⾯,申请后返回申请整个内存的起始地址,然后将⽤户申请的多少字节返回给⽤户,其他空间由malloc函数库(⽤户态)来管理,此后⽤户申请的⼩内存不需要要向OS申请。
malloc(0)也会申请内存资源
malloc(-10) 拒接不做任何处理
int*p =(int*)malloc(sizeof(int)*10);
if(NULL==p)exit(1);//malloc申请之后,⼀定要记得检测其是否申请成功(为了多线程安全)!
申请空间时,为其分配了40个字节的空间作为数据空间(即⽤户需要的),还分配了8个字节的越界标记,这个应该和栈的carray技术⼀样,检查越界的,所以发⽣越界错误时,是在free时才会检测出来错误,free也只是在调⽤free时才发现才去即时制⽌。同时还会分配头部空间的字节,来记录malloc分配的空间⼤⼩20字节 40+8+20=40+28,即系统还会多提供32字节的内存(头部空间⼤⼩不,⼀般int 20 char 24)
free的越界标记是针对于malloc函数库的,标记位是⾯向系统的。
同时free其实进⾏了堆空闲块的合并
2.malloc函数的相关堆分配算法
在这种情况中**,⼀个块是由⼀个字的头部、有效载荷,以及可能的⼀些额外的填充组成的。头部编码了这个块的⼤⼩(包括头部和所有的填充),以及这个块是已分配的还是空闲的。如果我们强加⼀个双字的对齐约束条件**,那么块⼤⼩就总是8的倍数,且块⼤⼩的最低3位总是零。因此,我们只需要内存⼤⼩的29个⾼位,释放剩余的3位来编码其他信息。在这种情况中,我们⽤其中的最低位(已分配位)来指明这个块是已分配的还是空闲的。例如,假设我们有⼀个已分配的块,⼤⼩24(0x18)字节。那么它的头部将是
0x00000018 l 0x1 = 0x00000019
类似地,⼀个块⼤⼩为40(Ox28)字节的空闲块有如下的头部:
0x00000028 I 0x0 = 0x00000028
头部后⾯就是应⽤调⽤malloc时请求的有效载荷。有效载荷后⾯是⼀⽚不使⽤的填充块,其⼤⼩可以是任意的。需要填充有很多原因。⽐如,填充可能是分配器策略的⼀部分,⽤来对付外部碎⽚。或者也需要⽤它来满⾜对齐要求。
molloc函数隐式空闲链表
我们称这种结构为隐式空闲链表,是因为空闲块是通过头部中的⼤⼩字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从⽽间接地遍历整个空闲块的集合。注意,我们需要某种特殊标记的结束块,在这个⽰例中,就是⼀个设置了已分配位⽽⼤⼩为零的终⽌头部(ter-minating header)。
隐式空闲链表的优点是简单。显著的缺点是任何操作的开销,例如放置分配的块,要求对空闲链表进⾏搜索,该搜索所需时间与堆中已分配块和空闲块的总数呈线性关系。
很重要的⼀点就是意识到系统对齐要求和分配器对块格式的选择会对分配器上的最⼩块⼤⼩有强制的要求。没有已分配块或者空闲块可以⽐这个最⼩值还⼩。例如,如果我们假设⼀个双字的对齐要求,那么每个块的⼤⼩都必须是双字(8字节)的倍数。因此,图9-35中的块格式就导致最⼩的块⼤⼩为两个字:⼀个字作头,另⼀个字维持对齐要求。即使应⽤只请求⼀字节,分配器也仍然需要创建⼀个两字的块。
3.malloc申请失败,即可使⽤的内存满时应该怎么办?
#include<stdlib.h>
#include<pthread.h>
struct foo {
int f_count;
pthread_mutex_t f_lock;
/* more stuff here */
};
struct foo *
foo_alloc(void)/* allocate the object */
{
struct foo *fp;
if((fp =malloc(sizeof(struct foo)))!=NULL)
{
fp->f_count =1;
if(pthread_mutex_init(&fp->f_clock,NULL)!=0)
{
free(fp);
return(NULL);
}
/* continue initialization */
}
return(fp);
}
void
foo_hold(struct foo *fp)/* add a reference to the object */
{
pthread_mutex_lock(&fp->f_clock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void
foo_rele(struct foo *fp)/* release a reference to the object */
{
pthread_mutex_lock(&fp->f_lock);
if(--fp->f_count ==0)/* last reference */
{
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}
else
{
pthread_mutex_unlock(&fp->f_lock);
}
}
对于多个对象利⽤堆区时。
在使⽤该对象前,线程需要对这个对象的引⽤计数加1,当对象使⽤完毕时,需要对引⽤计数减1。当最后⼀个引⽤被释放时,对象所占的内存空间就被释放。在对引⽤计数加1、减1以及检查引⽤计数是否为0这些操作之前需要锁住互斥量。
当堆内存满时,可以利⽤虚拟内存来进⾏。
虚拟内存(计算机系统内存管理)(cache和主存构成了系统的内存,⽽主存和辅存依靠辅助软硬件的⽀持构成了虚拟存储器。)
虚拟内存是计算机系统内存管理的⼀种技术。它使得应⽤程序认为它拥有连续的可⽤的内存(⼀个连续完整的地址空间),⽽实际上,它通常是被分隔成多个物理内存碎⽚,还有部分暂时存储在外部磁盘存储器上,在需要时进⾏数据交换。⽬前,⼤多数操作系统都使⽤了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。
虚拟内存别称虚拟存储器(Virtual Memory)。电脑中所运⾏的
程序均需经由内存执⾏,若执⾏的程序占⽤内存很⼤或很多,则会导致内存消耗殆尽。为解决该问题,Windows中运⽤了虚拟内存技术,即匀出⼀部分硬盘空间来充当内存使⽤。当内存耗尽时,电脑就会⾃动调⽤硬盘来充当内存,以缓解内存的紧张。若计算机运⾏程序或操作所需的随机存储器(RAM)不⾜时,则 Windows 会⽤虚拟存储器进⾏补偿。它将计算机的RAM和硬盘上的临时空间组合。当RAM运⾏速率缓慢时,它便将数据从RAM移动到称为“分页⽂件”的空间中。将数据移⼊分页⽂件可释放RAM,以便完成⼯作。 ⼀般⽽⾔,计算机的RAM容量越⼤,程序运⾏得越快。若计算机的速率由于RAM可⽤空间匮乏⽽减缓,则可尝试通过增加虚拟内存来进⾏补偿。但是,计算机从RAM读取数据的速率要⽐从硬盘读取数据的速率快,因⽽扩增RAM容量(可加内存条)是最佳选择。
⼯作原理
虚拟存储器是由硬件和操作系统⾃动实现存储信息调度和管理的。它的⼯作过程包括6个步骤:
①中央处理器访问主存的逻辑地址分解成组号a和组内地址b,并对组号a进⾏地址变换,即将逻辑组号a作为索引,查地址变换表,以确定该组信息是否存放在主存内。
②如该组号已在主存内,则转⽽执⾏④;如果该组号不在主存内,则检查主存中是否有空闲区,如果没有,便将某个暂时不⽤的组调出送往辅存,以便将这组信息调⼊主存。
③从辅存读出所要的组,并送到主存空闲区,然后将那个空闲的物理组号a和逻辑组号a登录在地址变换表中。
④从地址变换表读出与逻辑组号a对应的物理组号a。 [
⑤从物理组号a和组内字节地址b得到物理地址。
⑥根据物理地址从主存中存取必要的信息。
1 内存泄露是什么意思?
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或⽆法释放,造成系统内存的浪费,导致程序运⾏速度减慢甚⾄系统崩溃等严重后果。(程序还在运⾏,没有退出。)
内存泄漏缺陷具有隐蔽性、积累性的特征,⽐其他内存⾮法访问错误更难检测。因为内存泄漏的产⽣原因是内存块未被释放,属于遗漏型缺陷⽽不是过错型缺陷。此外,内存泄漏通常不会直接产⽣可观察的错误症状,⽽是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。
2 哪些常见⾏为会导致内存泄露?
内存泄露不是简单的没有进⾏delete和free
1.丢失地址
2.运⾏的堆空间满了,就将所有的内存空间都申请完了,⽆法执⾏了,系统甚⾄崩溃(系统⽆法回写数据),且此时内存空间⽆法释放?
3.对象没有析构,直接调⽤free进⾏释放?是因为标记位没有处理吗? 没有虚函数时可以偷懒直接进⾏空间对对象的赋值吗?
3 造成的内存泄露程序结束时,内存会释放吗?
操作系统回收资源是指,当程序(进程)结束后,程序所使⽤的资源都会被操作系统回收。但是如果程序⼀直在运⾏的话, 如果你不主动释放空间的话,⽤new申请的空间是不会被回收的。此时将标记位改变。
4.对堆内存的管理
1.堆内存申请释放,利⽤线程,⼀个线程来统计其空间被多少线程使⽤,另⼀个线程判断其空间没⼈使⽤则释放,需要读写锁。
2.堆内存申请释放,利⽤继承机制,⽗类来申请空间,⼦类使⽤空间,⼦类析构,⽗类释放空间(利⽤栈机制,但⼀般理解⼦类对象为构造时,先对⽗类构造,再对⼦类构造,析构时,先对⼦类析构,再对⽗类释放) 即⽗类和⼦类的责任分离,⼀个负责申请释放,⼀个负责使⽤析构。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论