STL源码学习之空间配置器(allocator)

空间配置器的标准接口

根据stl规范,以下是空间配置器的标准接口

	  typedef _Tp        value_type;
      typedef size_t     size_type;
      typedef ptrdiff_t  difference_type;
      
      // These were removed for C++20.
      typedef _Tp*       pointer;
      typedef const _Tp* const_pointer;
      typedef _Tp&       reference;
      typedef const _Tp& const_reference;


      allocator::rebind()   // typdef allocator<U> other
      allocator::allocator()   	// default constructor
      allocator::allocator(const allocator&) 	// copy constructor
      tempate<U> allocator::allocator(const allocator<U>&)   	// generalization  constructor
      allocator::~allocator() 		// default destructor
      pointer address(reference x)
      const_pointer const_address(const_reference x) 
      pointer allocate(size_type n, const void* hint=0)
      void deallocate(pointer p, size_type n)
      void construct(pointer p, const T& value) 
      void destroy(pointer p)
      size_type max_size()

具备次配置力的SGI空间配置器

SGI STL的配置的名称为alloc,不接受任何参数

vector<int,allocator<int> > iv;  // in VC or CB
vector<int, alloc> iv; 				// in gcc

SGI标准空间配置器,std::allocator

为了精密分工,内存操作由std::allocate() 负责,内存释放由std::deallocte() 负责,对象构造操作由 ::construct() 负责,对象析构操作由 ::destory() 负责。

construct接受一个指针和一个初值value,该函数的用途就是将该值设定到指针所指的空间。
destory有两个版本,第一版本接受一个指针,将所指之物释放掉。第二版本接受firstlast两个迭代器,将迭代器内所有的对象析构掉
construct() 与 destopry() 示意

SGI特殊空间配置器,std::alloc <std_alloc.h>

SGI设计的要求

  1. system heap 要求空间
  2. 考虑多线程 (multi-threads) 状态
  3. 考虑内存不足时的应变措施
  4. 考虑过多“小型区块”可能造成的内存碎片问题

考虑到小型区块可能造成的内存破碎问题,SGI设计了双层级配置器,第一配置器直接使用malloc()free() 第二级配置器则视情况不同采用不同的策略。当配置器超过128 bytes 时,采用一级配置器;当小于128 bytes 时,则视为过小,为了降低额外的负担,便采取 memory pool 整理方式。(是否采用两级配置器,看 __USE_MALLOC 是否被定义)
在这里插入图片描述
在这里插入图片描述

第一级配置器 __malloc_alloc_template 剖析

new_handler 的机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。换句话说,一旦 :operator new 无法完成任务,在丢出 std::bad_alloc 异常之前,会调用由客端指定的处理例程。该处理例程通常被称为 new_handle

第一级配置器以 malloc(), free(), realloc() 等C函数执行实际的内存配置、释放、重配操作,并实现类似 C++ new_handler 机制,因为它并非使用 ::operator new 来配置内存。
SGI以 malloc 而非 ::operator new 来配置内存,因此,SGI 不能直接使用C++的 set_new_handler() ,必须仿真一个类似的 set_malloc_handler()

SGI 第一配置器的 allocte()realloc() 不成功后,改调用 oom_malloc() 和 ·oom_realloc() 。两者内部都有内循环,不断调用 “内存不足处理例程” ,期望某次调用后,获得足够的内存而圆满完成任务。但如果 “内存不足处理例程” 并未被客端设定,oom_malloc() 和 ·oom_realloc() 便会毫不客气地调用 __THROW_BAD_ALLOC ,丢出异常信息。

第二级配置器 __default_alloc_template 剖析
第二级配置器多了一些机制,避免太多小额区块造成内存碎片。小额区块带来的不只内存碎片,配置时额外负担也是一个大问题。

第二级配置的做法是,当区块小于 128bytes 时,则以内存池管理。每次配置一大块内存,并维护对应的自由链表,下次若再有相同大小的内存需求,就直接从 free_list 中拔出。如果客端释还小额区块,由配置器回收到 free_list ,为了方便管理。SGI 第二级配置器会主动将任何小额区块的内存需求上调到 8 的倍数,并维护 16 个 free_listfree_list 节点结构如下

union obj{
	union obj* free_list_link;
	char client_data[1];				/* The client sees this. */
}

上述 obj 所用的是 union ,由于 union 之故,从第一字段观之, obj 可被视为一个指针,指向相同形式的另一个 obj 。从第二字段观之, obj 可被视为一个指针,指向实际区块。
在这里插入图片描述
请添加图片描述
请添加图片描述
allocator() 中,如果 free_list 没有可用区块,那么就会调用 refill() 重新填充 free_list

// 以下为第二级配置器
// 无模板参数,且inst没用,第一个参数用于多线程情况
template <bool threads, int inst>
class __default_alloc_template {
private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
  // 向上取整至8的倍数(取反加一的逆操作)
  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

__PRIVATE:
  // 节点构造
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
private:
// 16个free_list
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
        // Specifying a size results in duplicate def for 4.1
# else
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
# endif
  // n从1算起,根据区块大小决定使用第n号free_list
  static  size_t _S_freelist_index(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }

  // 传回一个大小为 n的对象,并可能加入大小为 n的其它区块到  free_list
  // Returns an object of size __n, and optionally adds to size __n free list.
  static void* _S_refill(size_t __n);
  // 配置㆒大块空间,可容纳 nobjs个大小为 "size"的区块。
  // 如果配置 nobjs个区块有所不便,nobjs可能会降低。
  // Allocates a chunk for nobjs of size size.  nobjs may be reduced
  // if it is inconvenient to allocate the requested number.
  static char* _S_chunk_alloc(size_t __size, int& __nobjs);
  
  // Chunk allocation state.
  static char* _S_start_free; //记忆池起始位置。只在 chunk_alloc()㆗变化
  static char* _S_end_free;  //记忆池结束位置。只在 chunk_alloc()㆗变化
  static size_t _S_heap_size;

# ifdef __STL_THREADS
    static _STL_mutex_lock _S_node_allocator_lock;
# endif

    // It would be nice to use _STL_auto_lock here.  But we
    // don't need the NULL check.  And we do need a test whether
    // threads have actually been started.
    class _Lock;
    friend class _Lock;
    class _Lock {
        public:
            _Lock() { __NODE_ALLOCATOR_LOCK; }
            ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }
    };

public:

  /* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    // 大于128就调用第一级配置器
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      // 寻找16个free_list中适当的一个
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#     endif
      _Obj* __RESTRICT __result = *__my_free_list;
      // 没找到可用的free_list,重新填充free_list
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        // 调整free_list
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

  /* __p may not be 0 */
  static void deallocate(void* __p, size_t __n)
  {
    // 大于128调用第一级配置器
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    else {
      // 寻找对应的free_list并调整free_list,挥手区块
      _Obj* __STL_VOLATILE*  __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      _Obj* __q = (_Obj*)__p;

      // acquire lock
#       ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#       endif /* _NOTHREADS */
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
    }
  }

  static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);

} ;


template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs)
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;
    size_t __bytes_left = _S_end_free - _S_start_free;

    // 记忆池剩余空间满足需求
    if (__bytes_left >= __total_bytes) {
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    // 剩余一个以上空间
    } else if (__bytes_left >= __size) {
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    // 剩余空间一个区块大小都不足
    } else {
        size_t __bytes_to_get = 
	  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        // 尝试把剩余的先放到适当的free_list中
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }

        // 配置heap空间,分配内存到记忆池中
        _S_start_free = (char*)malloc(__bytes_to_get);
        if (0 == _S_start_free) {
            // 堆空间都不足,malloc失败情况
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
	    _Obj* __p;
            // 找free_list里面足够大的区块先分配给调用者
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                if (0 != __p) {
                    // 调整free_list释放区块
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    // 递归调用自己,为了修正 nobjs。
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                    //注意,任何残余零头终将被编入适当的  free-list备用。
                }
            }
	    _S_end_free = 0;	// In case of exception.
            // 呼叫第一级配置器,使用oom机制尽人事
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
            // 这会导致掷出异常(exception),或内存不足的情况获得改善
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        //递归呼叫自己,为了修正 nobjs。
        return(_S_chunk_alloc(__size, __nobjs));
    }
}


// 传回㆒个大小为 n的对象,并且有时候会为适当的free list增加节点. 
// 假设 n已经适当上调至 8的倍数。
/* Returns an object of size __n, and optionally adds to size __n free list.*/
/* We assume that __n is properly aligned.                                */
/* We hold the allocation lock.                                         */
template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20;
    //呼叫 chunk_alloc(),尝试取得 nobjs个区块做为free list的新节点。
    //注意参数 nobjs是  pass by reference。
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    // 只获得一个区块,则返回给调用者,free_list无新节点
    if (1 == __nobjs) return(__chunk);
    // 调整free_list,加入新节点
    __my_free_list = _S_free_list + _S_freelist_index(__n);

    // 在chunk空间内建立free_list
    /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      // free_list指向新配置的空间(取自记忆池)
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      // free_list节点串联,从1开始,0传回调用者
      for (__i = 1; ; __i++) {
        __current_obj = __next_obj;
        __next_obj = (_Obj*)((char*)__next_obj + __n);
        if (__nobjs - 1 == __i) {
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

内存基本处理工具

STL 定义有五个全局函数,作用于未初始化空间上。分别是用于构造的 construct() 和用于析构 destroy() ,另外三个函数是 uninitialized_copy() uninitialized_fill() uninitialized_fill_n(),SGI把它们定义于 <stl_uninitialized>

uninitialized_copy()函数用于将一个范围内的元素复制到另一个范围内的位置。它接受两个迭代器作为参数,指定要复制的范围,以及目标范围的起始迭代器。它会根据容器的特性,自动判断目标范围是否足够容纳要复制的元素,并进行复制操作。如果目标范围无法容纳复制的元素,或者未初始化目标范围,则该函数的行为是未定义的。

uninitialized_fill()函数用于在容器中填充指定值。它接受两个迭代器作为参数,指定要填充的范围,以及要填充的值。它会根据容器的特性,自动判断目标范围是否足够容纳要填充的元素,并进行填充操作。如果目标范围无法容纳填充的元素,或者未初始化目标范围,则该函数的行为是未定义的。

uninitialized_fill_n()函数用于在容器中填充指定数量的元素。它接受三个迭代器作为参数,指定要填充的范围,以及要填充的数量。它会根据容器的特性,自动判断目标范围是否足够容纳要填充的元素,并进行填充操作。如果目标范围无法容纳填充的元素,或者未初始化目标范围,则该函数的行为是未定义的。
请添加图片描述

malloc() 与 free()

进程地址空间

在这里插入图片描述

如上图所示在一个32位系统中,可寻址的空间大小是4G,linux系统下0-3G是用户模式,3-4G是内核模式。而在用户模式下又分为代码段、数据段、.bss段、堆、栈。其中代码段主要存放进程的可执行二进制代码,字符串字面值和只读变量。数据段存放已经初始化且初始值非0的全局变量和局部静态变量。bss段则存放未初始化或初始值为0的全局变量和局部静态变量。而堆段则是存放由用户动态分配内存存储的变量。栈段则主要存储局部变量、函数参数、返回地址等。
bss段、数据段和代码段是可执行程序编译时的分段,运行时还需要栈和堆。将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和bss段的加载,并在内存中为这些段分配空间。栈也由操作系统分配和管理而堆则是由程序员自己管理。

内存映射段(mmap)的作用是:内核将硬盘文件的内容直接映射到内存,任何应用程序都可通过 Linux 的 mmap() 系统调用请求这种映射。

  1. 内存映射是一种方便高效的文件 I/O 方式, 因而被用于装载动态共享库。
  2. 用户也可创建匿名内存映射,该映射没有对应的文件,可用于存放程序数据。
  3. 在 Linux 中,若通过 malloc() 请求一大块内存,C 运行库将创建一个匿名内存映射,而不使用堆内存。“大块”意味着比阈值MMAP_THRESHOLD还大,缺省为 128KB,可通过 mallopt() 调整。
  4. mmap 映射区向下扩展,堆向上扩展,两者相对扩展,直到耗尽虚拟地址空间中的剩余区域。

在Linux中进程由进程控制块(PCB)描述,用一个task_struct 数据结构表示,这个数据结构记录了所有进程信息,包括进程状态、进程调度信息、标示符、进程通信相关信息、进程连接信息、时间和定时器、文件系统信息、虚拟内存信息等. 和malloc密切相关的就是虚拟内存信息,定义为struct mm_struct *mm 具体描述进程的地址空间。mm_struct结构是对整个用户空间(进程空间)的描述

///include/linux/sched.h 
​
struct mm_struct {
  struct vm_area_struct * mmap;  /* 指向虚拟区间(VMA)链表 */
  rb_root_t mm_rb;         /*指向red_black树*/
  struct vm_area_struct * mmap_cache;     /* 指向最近找到的虚拟区间*/
  pgd_t * pgd;             /*指向进程的页目录*/
  atomic_t mm_users;                   /* 用户空间中的有多少用户*/                                     
  atomic_t mm_count;               /* 对"struct mm_struct"有多少引用*/                                     
  int map_count;                        /* 虚拟区间的个数*/
  struct rw_semaphore mmap_sem;
  spinlock_t page_table_lock;        /* 保护任务页表和 mm->rss */       
  struct list_head mmlist;            /*所有活动(active)mm的链表 */
  unsigned long start_code, end_code, start_data, end_data; /* 代码段、数据段 起始地址和结束地址 */
  unsigned long start_brk, brk, start_stack; /* 栈区 的起始地址,堆区 起始地址和结束地址 */
  unsigned long arg_start, arg_end, env_start, env_end; /*命令行参数 和 环境变量的 起始地址和结束地址*/
  unsigned long rss, total_vm, locked_vm;
  unsigned long def_flags;
  unsigned long cpu_vm_mask;
  unsigned long swap_address;
​
  unsigned dumpable:1;
  /* Architecture-specific MM context */
  mm_context_t context;
};

其中start_brk和brk分别是堆的起始和终止地址,我们使用malloc动态分配的内存就在这之间。start_stack是进程栈的起始地址,栈的大小是在编译时期确定的,在运行时不能改变。而堆的大小由start_brk 和brk决定,但是可以使用系统调用sbrk() 或brk()增加brk的值,达到增大堆空间的效果,但是系统调用代价太大,涉及到用户态和内核态的相互转换。所以,实际中系统分配较大的堆空间,进程通过malloc()库函数在堆上进行空间动态分配,堆如果不够用malloc可以进行系统调用,增大brk的值。

相关系统调用
  • brk()和sbrk()

由之前的进程地址空间结构分析可以知道,要增加一个进程实际的可用堆大小,就需要将break指针向高地址移动。Linux通过brk和sbrk系统调用操作break指针。两个系统调用的原型如下:

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

进程所面对的虚拟内存地址空间,只有按页映射到物理内存地址,才能真正使用。受物理存储容量限制,整个堆虚拟内存空间不可能全部映射到实际的物理内存。因此每个进程有一个rlimit表示当前进程可用的资源上限。这个限制可以通过getrlimit系统调用得到。

struct rlimit {
  rlim_t rlim_cur;  /* Soft limit */
  rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */
};
mmap函数
#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);

mmap函数第一种用法是映射磁盘文件到内存中;而malloc使用的mmap函数的第二种用法,即匿名映射,匿名映射不映射磁盘文件,而是向映射区申请一块内存。 munmap函数是用于释放内存,第一个参数为内存首地址,第二个参数为内存的长度。接下来看下mmap函数的参数。

当申请小内存的时,malloc使用sbrk分配内存;当申请大内存时,使用mmap函数申请内存;但是这只是分配了虚拟内存,还没有映射到物理内存,当访问申请的内存时,才会因为缺页异常,内核分配物理内存。

malloc实现方案

malloc采用的是内存池的实现方式
在这里插入图片描述
内存池保存在bins这个长128的数组中,每个元素都是一双向个链表。

  • bins[0]目前没有使用
  • bins[1]的链表称为unsorted_list,用于维护free释放的chunk。
  • bins[2,63)的区间称为small_bins,用于维护<512字节的内存块,其中每个元素对应的链表中的chunk大小相同,均为index*8。
  • bins[64,127)称为large_bins,用于维护>512字节的内存块,每个元素对应的链表中的chunk大小不同,index越大,链表中chunk的内存大小相差越大,例如: 下标为64的chunk大小介于[512, 512+64),下标为95的chunk大小介于[2k+1,2k+512)。同一条链表上的chunk,按照从小到大的顺序排列。

malloc除了有unsorted binsmall binlarge bin三个bin之外,还有一个fast bin。一般的情况是,程序在运行时会经常需要申请和释放一些较小的内存空间。当分配器合并了相邻的几个小的 chunk 之后,也许马上就会有另一个小块内存的请求,这样分配器又需要从大的空闲内存中切分出一块,这样无疑是比较低效的,故而,malloc 中在分配过程中引入了 fast bins,不大于 max_fast(默认值为 64B)的 chunk 被释放后,首先会被放到 fast bins中,fast bins 中的 chunk 并不改变它的使用标志 P。这样也就无法将它们合并,当需要给用户分配的 chunk 小于或等于 max_fast 时,malloc 首先会在 fast bins 中查找相应的空闲块,然后才会去查找 bins 中的空闲 chunk。在某个特定的时候,malloc 会遍历 fast bins 中的 chunk,将相邻的空闲 chunk 进行合并,并将合并后的 chunk 加入 unsorted bin 中,然后再将 unsorted bin 里的 chunk 加入 bins 中。
unsorted bin 的队列使用 bins 数组的第一个,如果被用户释放的 chunk 大于 max_fast,或者 fast bins 中的空闲 chunk 合并后,这些 chunk 首先会被放到 unsorted bin 队列中,在进行 malloc 操作的时候,如果在 fast bins 中没有找到合适的 chunk,则malloc 会先在 unsorted bin 中查找合适的空闲 chunk,然后才查找 bins。如果 unsorted bin 不能满足分配要求。 malloc便会将 unsorted bin 中的 chunk 加入 bins 中。然后再从 bins 中继续进行查找和分配过程。从这个过程可以看出来,unsorted bin 可以看做是 bins 的一个缓冲区,增加它只是为了加快分配的速度。(其实感觉在这里还利用了局部性原理,常用的内存块大小差不多,从unsorted bin这里取就行了,这个和TLB之类的都是异曲同工之妙啊!

除了上述四种bins之外,malloc还有三种内存区。

当fast bin和bins都不能满足内存需求时,malloc会设法在top chunk中分配一块内存给用户;top chunk为在mmap区域分配一块较大的空闲内存模拟sub-heap。(比较大的时候) >top chunk是堆顶的chunk,堆顶指针brk位于top chunk的顶部。移动brk指针,即可扩充top chunk的大小。当top chunk大小超过128k(可配置)时,会触发malloc_trim操作,调用sbrk(-size)将内存归还操作系统。

当chunk足够大,fast bin和bins都不能满足要求,甚至top chunk都不能满足时,malloc会从mmap来直接使用内存映射来将页映射到进程空间,这样的chunk释放时,直接解除映射,归还给操作系统。(极限大的时候)

Last remainder是另外一种特殊的chunk,就像top chunk和mmaped chunk一样,不会在任何bins中找到这种chunk。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需要的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。(这个应该是fast bins中也找不到合适的时候,用于极限小的)
在这里插入图片描述

由之前的分析可知malloc利用chunk结构来管理内存块,malloc就是由不同大小的chunk链表组成的。malloc会给用户分配的空间的前后加上一些控制信息,用这样的方法来记录分配的信息,以便完成分配和释放工作。chunk指针指向chunk开始的地方,图中的mem指针才是真正返回给用户的内存指针。

  • chunk 的第二个域的最低一位为P,它表示前一个块是否在使用中,P 为 0 则表示前一个 chunk 为空闲,这时chunk的第一个域 prev_size 才有效,prev_size 表示前一个 chunk 的 size,程序可以使用这个值来找到前一个 chunk 的开始地址。当 P 为 1 时,表示前一个 chunk 正在使用中,prev_size程序也就不可以得到前一个 chunk 的大小。不能对前一个 chunk 进行任何操作。malloc分配的第一个块总是将 P 设为 1,以防止程序引用到不存在的区域。(这里就很细!)
  • Chunk 的第二个域的倒数第二个位为M,他表示当前 chunk 是从哪个内存区域获得的虚拟内存。M 为 1 表示该 chunk 是从 mmap 映射区域分配的,否则是从 heap 区域分配的。
  • Chunk 的第二个域倒数第三个位为 A,表示该 chunk 属于主分配区或者非主分配区,如果属于非主分配区,将该位置为 1,否则置为 0。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值