malloc申请堆内存

8 篇文章 0 订阅
7 篇文章 0 订阅

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);
ifNULL==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时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。需要填充有很多原因。比如,填充可能是分配器策略的一部分,用来对付外部碎片。或者也需要用它来满足对齐要求。

隐式空闲链表

在这里插入图片描述

我们称这种结构为隐式空闲链表,是因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。注意,我们需要某种特殊标记的结束块,在这个示例中,就是一个设置了已分配位而大小为零的终止头部(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得到物理地址。
⑥根据物理地址从主存中存取必要的信息。

https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98/101812?fr=aladdin

1 内存泄露是什么意思?

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。(程序还在运行,没有退出。)
内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。

2 哪些常见行为会导致内存泄露?

内存泄露不是简单的没有进行delete和free
1.丢失地址
2.运行的堆空间满了,就将所有的内存空间都申请完了,无法执行了,系统甚至崩溃(系统无法回写数据),且此时内存空间无法释放?
3.对象没有析构,直接调用free进行释放?是因为标记位没有处理吗? 没有虚函数时可以偷懒直接进行空间对对象的赋值吗?

3 造成的内存泄露程序结束时,内存会释放吗?

操作系统回收资源是指,当程序(进程)结束后,程序所使用的资源都会被操作系统回收。但是如果程序一直在运行的话, 如果你不主动释放空间的话,用new申请的空间是不会被回收的。此时将标记位改变。

4.对堆内存的管理

1.堆内存申请释放,利用线程,一个线程来统计其空间被多少线程使用,另一个线程判断其空间没人使用则释放,需要读写锁。

2.堆内存申请释放,利用继承机制,父类来申请空间,子类使用空间,子类析构,父类释放空间(利用栈机制,但一般理解子类对象为构造时,先对父类构造,再对子类构造,析构时,先对子类析构,再对父类释放) 即父类和子类的责任分离,一个负责申请释放,一个负责使用析构。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
华为OD机试:堆内存申请 在华为OD机试中,堆内存申请是其中一个重要的考点。堆内存是程序运行时动态分配的内存空间,用于存储程序运行中产生的数据。 在C/C++语言中,我们使用`malloc()`函数来申请堆内存。`malloc()`函数接受一个参数,表示要申请的内存空间大小(单位是字节),并返回一个指向申请到的堆内存的指针。 例如,我们可以使用以下代码申请一个大小为100字节的堆内存空间: ``` int* ptr = (int*)malloc(100); ``` 在申请堆内存后,我们可以通过指针`ptr`来访问和操作这段堆内存空间。还需要注意的是,申请到的堆内存空间在使用完毕后,需要使用`free()`函数来释放它,以防止内存泄漏: ``` free(ptr); ``` 另外,为了避免内存泄漏和悬挂指针等问题,我们还需要注意以下几点: 1. 在申请堆内存时,应该检查`malloc()`函数返回的指针是否为`NULL`,以确保内存申请成功。 2. 在使用完申请到的内存后,一定要记得使用`free()`函数来释放内存,避免内存泄漏。 3. 避免使用已经释放的内存,也就是悬挂指针的问题,这样的操作可能导致程序崩溃或非预期的行为。 总之,在华为OD机试中,对于堆内存申请的考察主要包括如何正确申请和释放堆内存、避免悬挂指针和内存泄漏的问题。在编写代码时,我们应该牢记这些原则,以确保程序的正确性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值