malloc和free

本文详细介绍了C/C++中malloc和free的内存管理机制,通过glibc源代码分析了内存分配的过程,指出并非每次malloc都需要进行系统调用,并探讨了fastbin、smallbin等内存分配策略,揭示了内存分配的内部细节。
摘要由CSDN通过智能技术生成

昨天看见一位同学分享面试经历

中间有这么一句话:”一是Java的内存分配原理与C/C++不同,C/C++每次采用malloc或new申请内存时都要进行brk和mmap等系统调用,而系统调用发生在内核空间,每次都要中断进行切换,这需要一定的开销“。

因为这个同学最终拿到了阿里offer,且这个回答得到了面试官”(面试官一直点头表示对我回答的赞同)嗯,看来你对这块的确掌握了“的回应,不知道是这位同学没有详查面试官的反应还是面试官确实给出了比较positive的答复,心里有点复杂,阿里一直是哥的神啊。

java不了解,没有发言权,对于glibc的内存管理机制还是了解的,个人这个回答起码是不精确的。

问了下身边同事,有些不太清楚,有些认为是直接跟OS要的,有些知道个大概,但是能完完整整的把底层机制解释出来的几乎没有。

所以今天特意结合glibc源代码解释一下这个问题。

关于malloc和free,linux所使用的ptmalloc在glibc的malloc/malloc.c中。

首先,我们熟知的malloc和free原型在这个文件里是找不到的,glibc很恶心的一点就是编译选项太多,因为确实一套代码需要支持的东西太多了,而且已经用了数十年,各种风格。

malloc是用宏定义的形式,在新旧版本函数名宏定义也不一样,不过新的glibc已经对此做了一些整理,类似控制采用libc_hidden_def/libc_hidden_proto两个宏,老的版本比较死板,可能维护者们也受不了了。

我办公室电脑上用的版本较老,具体什么版本记不清楚了,函数原型是void_t* public_mALLOc(size_t bytes)。

家里用的2.20,函数原型为void* __libc_malloc(size_t bytes)。不过函数体都是一样。

建议使用新版本阅读相关代码,逻辑要清晰些。

public_mALLOc也好,__libc_malloc也好,都是一个wrapper函数,是对_int_malloc(mstate av, size_t bytes)的封装,这才是整个内存分配的核心。

那就从外层开始


void *
__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

//检查是否有内存分配钩子,如存在,调用钩子,返回。
  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));

//获取分配区指针,失败返回。
  arena_lookup (ar_ptr);
  arena_lock (ar_ptr, bytes);
  if (!ar_ptr)
    return 0;

//调用_int_malloc分配内存
  victim = _int_malloc (ar_ptr, bytes);

//分配失败,
  if (!victim)
    {
      LIBC_PROBE (memory_malloc_retry, 1, bytes);

      //先跳到下面看一下这个arena_get_retry 
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      if (__builtin_expect (ar_ptr != NULL, 1))
        {

 //如果获取到有效分配区,分配内存,然后解锁
          victim = _int_malloc (ar_ptr, bytes);
          (void) mutex_unlock (&ar_ptr->mutex);
        }
    }
  else

     //成功分配内存,释放锁,返回地址
    (void) mutex_unlock (&ar_ptr->mutex);
  assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          ar_ptr == arena_for_chunk (mem2chunk (victim)));
  return victim;
}

/* If we don't have the main arena, then maybe the failure is due to running
   out of mmapped areas, so we can try allocating on the main arena.
   Otherwise, it is likely that sbrk() has failed and there is still a chance
   to mmap(), so try one of the other arenas.  */

//如果使用的不是主分配区,那么以上内存分配失败可能是分配区mmap区域内存耗尽。

//所以可以尝试从main分配区获取内存,就像是sbrk失败但是还是可以试试mmap。

//所以试试其他分配区
static mstate
arena_get_retry (mstate ar_ptr, size_t bytes)
{
  LIBC_PROBE (memory_arena_retry, 2, bytes, ar_ptr);

//不是主分配区
  if (ar_ptr != &main_arena)
    {

//释放当前分配区的分配锁
      (void) mutex_unlock (&ar_ptr->mutex);

     //获取主分配区地址

      ar_ptr = &main_arena;

     //加锁主分配区
      (void) mutex_lock (&ar_ptr->mutex);
    }
  else
    {

//如果是主分配区,试试其他分配区
      /* Grab ar_ptr->next prior to releasing its lock.  */
      mstate prev = ar_ptr->next ? ar_ptr : 0;
      (void) mutex_unlock (&ar_ptr->mutex);

      //获取其他分配区
      ar_ptr = arena_get2 (prev, bytes, ar_ptr);
    }


  return ar_ptr;
}

然后是真正的内存申请函数,在分配区av中分配一块bytes大小的内存

在介绍这个函数之前,先介绍一下一些重要的数据结构如下:

struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;             //分配区互斥锁
  /* Flags (formerly in max_fast).  */
  int flags;                        //
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];
  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;
  /* The remainder from the most recent split of a small request */因为小内存申请拆分的bin,保留其指针
  mchunkptr last_remainder;
  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];
  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];
  /* Linked list */
  struct malloc_state *next;
  /* Linked list for free arenas.  */
  struct malloc_state *next_free;
  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

/*
  This struct declaration is misleading (but accurate and necessary).
  It declares a "view" into memory allowing access to necessary
  fields at known offsets from a given base. See explanation below.
*/

//ptmalloc的内存表示形式
struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */chunk大小
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};



static void *
_int_malloc (mstate av, size_t bytes)
{
  INTERNAL_SIZE_T nb;               /* normalized request size */
  unsigned int idx;                 /* associated bin index */
  mbinptr bin;                      /* associated bin */


  mchunkptr victim;                 /* inspected/selected chunk */
  INTERNAL_SIZE_T size;             /* its size */
  int victim_index;                 /* its bin index */


  mchunkptr remainder;              /* remainder from a split */
  unsigned long remainder_size;     /* its size */


  unsigned int block;               /* bit map traverser */
  unsigned int bit;                 /* bit map traverser */
  unsigned int map;                 /* current word of binmap */


  mchunkptr fwd;                    /* misc temp for linking */
  mchunkptr bck;                    /* misc temp for linking */


  const char *errstr = NULL;


  /*
     Convert request size to internal form by adding SIZE_SZ bytes
     overhead plus possibly more to obtain necessary alignment and/or
     to obtain a size of at least MINSIZE, the smallest allocatable
     size. Also, checked_request2size traps (returning 0) request sizes
     that are so large that they wrap around zero when padded and
     aligned.
   */

//ptmalloc内部内存分配都是以chunk为单位,根据chunk大小,来决定如果获取相关chunk

//所以,在开始分配之前,先将需要分配的内存大小bytes转换成需要的chunk大小nb
  checked_request2size (bytes, nb);


  /*
     If the size qualifies as a fastbin, first check corresponding bin.
     This code is safe to execute even if av is not yet initialized, so we
     can try it without checking, which saves some time on this fast path.
   */

//如果nb不超过fastbin中最大chunk大小,尝试从fastbins中分配chunk

//那什么是fastbin呢,源代码中注释如下:

   

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值