(glibc 2.23) _int_malloc
_int_malloc에서 수행되는 코드의 흐름을 최대한 간략화하면 아래와 같다
- fastbin과 small bin에 재할당할 수 있는 청크 있으면 재할당
- unsorted bin에 last_remainder만이 존재할 경우 last_remainder를 재할당 시도
- unsortd bin을 차례차례 재할당 시도하고 실패 시 small bin과 large bin에 연결
- (2와 3번 과정 unsorted bin 비워질 때까지 또는 10000번 반복할 때까지 반복)
- large bin으로 재할당 시도
- binmap을 검사하여 재할당 시도
- top 청크를 사용하여 할당 시도
- sysmalloc으로 할당
전체 코드는 아래 더보기 (코드 양이 꽤 많다 에디터로 따로 코드를 볼수 있으면 열어보지 말자)
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.
*/
checked_request2size (bytes, nb);
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
/*
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.
*/
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.
The outer loop here is needed because we might not realize until
near the end of malloc that we should have consolidated, so must
do so and retry. This happens at most once, and only when we would
otherwise need to expand memory to service a "small" request.
*/
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.
The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
}
while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}
else
{
size = chunksize (victim);
/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb;
/* unlink */
unlink (av, victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).
We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/
victim = av->top;
size = chunksize (victim);
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}
인자 및 변수
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;
- av : 현재 아레나
- bytes : 할당 받을 크기
- nb : 요청할 크기
- idx : nb에 대한 힙 배열 인덱스 (fastbinsY 또는 bins)
- bin : bins[idx] - 0x10
- victim : 재할당에 사용될 청크 주소
- size : victim의 size
- victim_index : victim size에 대한 인덱스
- remainder : last_remainder를 사용한 뒤 남은 청크의 주소
- remainder_size : remainder의 size
- block : binmap의 block 값 ( binmap을 통해 freed chunk가 존재하는지 검사 및 재할당할 때 사용)
- bit : 인덱스에 해당하는 bit 값 ( binmap을 통해 freed chunk가 존재하는지 검사할 재할당 때 사용)
- map : binmap[block] 값을 저장
- fwd : fd 값 등을 저장
- bck : bk 값이 bins 주소 등을 저장
- errstr : 에러 문자열 저장
initial check
/*
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.
*/
checked_request2size (bytes, nb);
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
- check_request2size 매크로를 통해 요청한 사이즈가 정상 범위 내에 있으면 request2size 통해 할당받을 사이즈 값으로 가공 (MINSIZE 이상 그리고 정렬해서)
- arena가 NULL일 경우 sysmalloc으로 할당
fastbin
전체 코드는 아래 더보기
/*
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.
*/
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
변수
/*
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.
*/
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp = *fb;
nb가 fastbin 범위에 존재할 경우 수행된다
각 변수들에 저장되는 값은 다음과 같다
- idx : nb크기에 해당하는 fastbinsY 인덱스
- fb : &fastbinsY[idx] (인덱스에 해당하는 fastbinsY주소)
- pp : fastbinsY[idx] (마지막으로 해제된 chunk 주소)
fastbinsY 갱신
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
할당하려는 청크 크기에 해당하는 fastbinY에 freed청크가 존재하는지 확인하고 존재한다면 청크의 주소를 가져오고 인덱스의 주소를 갱신시킨다
추가 설명은 더보기
/* Atomically store NEWVAL in *MEM if *MEM is equal to OLDVAL.
Return the old *MEM value. */
#if !defined atomic_compare_and_exchange_val_acq \
&& defined __arch_compare_and_exchange_val_32_acq
# define atomic_compare_and_exchange_val_acq(mem, newval, oldval) \
__atomic_val_bysize (__arch_compare_and_exchange_val,acq, \
mem, newval, oldval)
#endif
catomic_compare_and_exchange_val_acq의 주석을 토대로 보면 *fb가 victim과 동일할 경우 victim->fd를 *fb에 저장하고 기존의 *fb를 반환해 pp에 저장한다
만약 *fb와 victim이 동일할 경우
- *fb = victim->fd
- pp = *fb
fastbin 재사용할 경우
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
- 재사용할 victim청크 size의 인덱스가 idx와 값이 동일한지 검사 ( 요청한 크기와 사이즈가 동일한지)
- victim청크 검사
- 포인터 p에 청크 헤더 제외한 주소 넣고 반환
small bin
전체 코드는 아래 더보기
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
initial
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
...
}
}
nb가 large bin의 최소 크기보다 작을 경우에 조건문이 수행된다
- idx에 nb 크기에 해당하는 bins배열의 인덱스를 저장
- bin에는 bins[idx] - 0x10 주소가 저장 ( bin[idx]의 fd와 bk를 사용하기 위함 )
- vitctim에 bin->bk를 저장하고 bin과 비교 free된 청크가 있는지 확인 (상세 과정 아래 더보기)

bins배열을 확인해보면 일정한 규칙성을 확인할 수있다 0x10byte 단위(인덱스)로 앞 인덱스의 주소가 저장되어 있다

대체 어떻게 bin->bk랑 bin의 주소를 가지고 free chunk가 존재하는지 그리고 bin_at에서 청크의 헤더 값을 빼주는지에 대한 이해를 하기 위해 bin_at의 동작 과정을 살펴봐야 한다
위의 그림으로 예를 들어 우리가 구한 인덱스 idx에 있는 freed chunk를 재사용한다고 했을 때 bin_at의 반환 값은 idx 인덱스 주소의 0x10을 뺀 값이 반환이 된다
반환된 주소의 ->fd, ->bk에 접근하면서 인덱스에 해당하는 fd와 bk값에 접근하게 된다
쉽게 말해 반환되는 주소를 chunk라고 생각하면 된다

디버깅을 통해 해당 과정을 보면 rax에 bins[idx]주소를 저장하고 rax+8에 저장된 값을 rdx에 저장한다
이것이 bin->bk이다 이후에 rax-0x10의 주소를 rsi에 저장하고 이를 비교하게 된다
결국 bin->bk와 bin[idx] - 0x10의 값을 비교하게 된다
bin[idx]에 값이 존재한다면 해당 과정은 당연히 참이 될 수 없지만 인덱스에 freed된 chunk가 없을 때는 참이 된다
참이 되는 이유는 처음에 bins배열에 값이 존재하지 않을 때 앞 인덱스의 주소를 저장하고 있기 때문에
rax-0x10은 bin[idx] - 0x10으로 cmp명령어가 참이 된다
단순히 0x20 size의 chunk라고 생각하면 수월하다
small bin reuse
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
이후에 재사용할 청크가 있는 bins배열을 갱신하고 사용할 청크를 반환한다
주요한 동작은 아래와 같다
- victim 0인지 체크 (0이면 malloc_consolidate호출)
- bck에 victim->bk 저장 bck->fd값이 victim인지 검사 (아니면 에러 뱉음)
- 재사용될 청크의 size에 inuse 플래그를 설정
- bins[idx]->bk에 victim->bk 저장 (재할당할 청크를 빼주기 위함)
- bck->fd에 bins[idx] - 0x10 저장 (새로 설정된 bk의 fd주소 갱신)
- 현재 아레나가 main아레나가 아니면 NON_MAIN_ARENA플래그 설정
- p에 재 할당될 청크 주소 저장 및 반환
large bin
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
small bin 크기인지 검사하는 if문이 통과되면 large bin이라고 판단하여 해당 코드가 수행된다
large bin을 재사용하기 위한 동작이 수행되진 않는다 (재할당 과정은 unsorted bin을 비운 뒤 이루어진다)
주요한 동작은 아래와 같다
- idx에 nb에 대항하는 인덱스를 구한다
- 현재 fastbin이 존재하면 이를 병합 시킴
using unsorted bin
루프를 돌면서 fastbin을 제외한 bin들을 뒤져서 재할당을 시도한다
해당 루프에서 수행되는 주요 동작을 간추리면 아래와 같다
- unsorted bin으로 재할당 시도
- large bin으로 재할당 시도
- binmap으로 재할당 가능한 bin 탐색해 재할당 시도
- top chunk으로 재할당 시도
- sysmalloc으로 재할당 시도
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
...
위 코드에서 수행되는 코드는 아래와 같다
- 반복문 변수 iters를 0으로 초기화한다
- victim에 unsorted bin의 bk를 저장하고 bin[0]-0x10과 동일한지 비교한다 (unsorted bin에 freed chunk가 존재하는지 검사)
unsorted bin이 존재할 경우 unsorted bin에 있는 freed chunk를 먼저 재할당 시도하고 실패하면 크기에 맞는 bin에 넣는 과정을 반복한다
수행되는 주요 동작을 간추리면 아래와 같다
- last_remainder으로 재할당 시도
- unsorted bin의 bk 재할당 시도
- bk 재할당 실패할 경우 bin에 연결
- 1 ~ 3번 과정을 unsorted bin에 freed chunk가 없을때까지 또는 10000번 루프가 돌 때까지 반복
(상세 과정은 아래 더보기)
unsorted bin에 freed chunk가 존재하면 while문내 코드를 수행하게 된다
size check
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);
- bck에 (bins[0]->bk)->bk를 저장 (2번째로 들어온 freed chunk)
- victim의 size가 정상적인 크기인지 검사 (범위 밖이면 에러를 뱉는다)
- size에 victim의 size를 저장
remainder reuse
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
...
small bin 크기의 요청이 들어오고 unsorted bin에 한 개의 청크만이 존재할 때 last reamainder를 사용하려 시도한다
아래 조건이 충족되면 조건문을 수행한다
- nb가 smallbin 크기
- unsorted bin이 1개인 경우
- victim이 last_remainder인 경우
- size가 요청이 들어온 청크의 크기 보다 클 경우
조건이 충족될 경우 last remainder를 쪼개서 재사용하는 과정을 수행한다
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
- remainder_size에 unsorted bin에 있는 chunk를 요청한 크기의 청크와 뺀 나머지 값을 저장
- remainder에 재사용 후 남은 freed 청크의 주소를 저장
- unsorted bin의 bk와 fd에 remainder를 저장 (unsorted bin은 한개 밖게 없기 때문)
- last_remainder를 remainder로 갱신
- remainder의 fd와 bk를 bin[0]-0x10으로 저장
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
- remainder_size가 large bin크기면 remainder의 fd_nextsize와 bk_nextsize를 0으로 초기화
- 재사용할 victim에 flag를 설정
- remainder에 PREV_INUSE비트를 설정하고 remainder 다음 청크의 prev_size를 변경
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
재사용할 청크 check하고 헤더 값을 제외한 주소를 포인터 p에 저장하고 반환
remove unsorted bin bk
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
last_remainder를 재사용하지 않을 경우 위 코드를 수행한다
- unsorted bin의 bk에 bck를 저장
- bck->fd에 bin[0]-0x10 저장
unsorted bin reuse
/* Take now instead of binning if exact fit */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
위에서 제거한 unsorted bin의 크기가 할당할 크기와 동일하면 victim을 재할당한다
reallocate failed (smallbin)
/* place chunk in bin */
if (in_smallbin_range (size)) // small bin인 경우
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else // large bin인 경우
{
...
}
위에서 재할당에 실패한 청크가 small bin에 해당되면 수행된다
- bck에 해당 인덱스 주소 저장
- fwd에 해당인덱스에 fd저장
large bin인 경우 else 문에서 처리된다
reallocate failed (large bin)
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
초입 부분은 small bin인 경우와 동일한 과정을 수행된다
- bck에 해당 인덱스 주소 저장
- fwd에 bins[인덱스]->fd 저장
/* maintain large bins in sorted order */
if (fwd != bck)
{
...
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
size에 해당하는 bins배열의 인덱스에 freed 청크가 존재하지 않는 경우 else문을 통해 victim의 fd_nextsize와 bk_nextsize에 victim의 주소를 저장한다
만약 freed 청크가 존재할 경우 if 조건문이 참이 되어 if문의 코드가 수행된다
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if문의 초입 부분에서는 size에 PREV_INUSE 비트를 설정 및 해당 bins배열 인덱스의 bk가 main_arena에 있는지 검사한다
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
...
}
bk에 있는 large bin의 크기가 size보다 큰 경우에 if문이 수행된다 (크기 단위로 sort하기 위해)
if문에서 fwd 와 bck를 수정하고 victim을 연결 리스트에 연결시킨다
victim을 연결하는 과정을 그림으로 보면 아래와 같다

위 그림은 victim을 large bin에 연결하기 전 large bin의 상황이다
여기서 victim을 연결할 때 수행되는 if문 코드는 아래와 같다
- fwd에 bck를 저장
- bck에 bck->bk저장
- victim의 fd_nextsize에 해당 인덱스의 fd저장
- victim의 bk_nextsize에 해당 인덱스의 fd의 bk_nextsize저장
- 해당 인덱스의 fd의 bk_nextsize와 victim->bk_nextsize->fd_nextsize를 victim으로 저장

if문의 코드를 통해 연결이 되면 large bin의 상태를 위와 같다
(숫자는 해당 순서일 때 변경되는 부분)
bk에 있는 large bin의 크기가 size와 같거나 큰 경우 else문이 수행되면서 아래 코드가 수행된다
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
위 코드에서 수행되는 과정은 아래와 같다
- main 아레나인지 검사
- while문을 돌면서 size보다 크기 않은 chunk를 크기의 내림차순으로 찾는다
- 찾은 청크의 size와 size가 동일하면 해당 청크의 fd를 fwd에 저장
- size가 더 클 경우 fwd앞에 victim을 연결한다
- bck에 fwd->bk를 저장한다
위 과정의 이해를 돕기 위해 위 2 케이스를 그림으로 보면 아래와 같다
(아래 그림들은 이해를 돕기 위한 그림으로 실제 저 크기들은 같은 bins에 연결되지 않는다)
same size

victim과 동일한 크기의 freed chunk가 있을때 victim을 bins에 연결하기 전 상황이다
크기가 같은 경우에는 fd_nextsize, bk_nextsize를 통해 연결하지 않고 fd와 bk로만 연결된다

그래서 연결 후 fd와 bk가 연결 된 것을 보면 다음과 같다
victim은 fd_nextsize와 bk_nextsize로 연결하지 않기 때문에 fd_nextsize와 bk_nexsize만 보면 0x480과 0x450가 연결되어있다

위의 설명을 뒷받침 하기 위한 디버깅 예시다
bigger size


정렬해서 fd, bk와 fd_nextsize, bk_nextsize 모두 bins에 연결한다
이로써 if문 과정이 종료된다
link fd, bk
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
victim의 fd, bk를 연결해준다
해당 while문을 1000번 반복한다
large bin으로 재할당
전체 코드는 아래 더보기
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
...
}
nb가 large bin에 해당하면 먼저 위에서 구한 idx로 bins주소를 구해 bin에 저장한다
해당 bins 인덱스의 fd가 nb와 같거나 크면 해당 bin을 뒤져 재할당하게 된다
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
...
}
victim에 bin->fd를 저장하고 해당 인덱스에 freed chunk가 존재하는지와 해당 인덱스 가장 큰 freed 청크(fd)의 size가 nb와 같거나 큰지 검사한다
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
작은 청크부터 큰 청크 순으로 재할당 할 nb보다 같거나 큰 청크를 찾아 unlink 한다
- 해당 bins배열에서 nb보다 크거나 같은 chunk가 나올때까지 while문을 돈다
- 위 과정을 통해 찾은 chunk를 victim을 저장
- victim이 bk가 아니고 victim과 같은 크기의 freed chunk가 있으면 그청크(victim->fd)로 갱신
- size와 nb를 빼 remainder_size를 구함
- 재할당할 victim unlink
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
...
}
remainder_size가 MINSIZE보다 작을 때 if문에서 수행되는 코드는 아래와 같다
- remainder chunk inuse비트를 설정한다
- 아레나를 확인하고 victim에 NON_MAIN_ARENA비트를 설정한다
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
remainder_size가 MINSIZE에서는 수행되는 코드는 아래와 같다
- remainder에 remainder chunk 주소를 저장
- bck에 bins의 첫번째 주소 저장 (bins[0] - 0x10)
- fwd에 unsorted bin의 fd 저장
- fwd->bk가 bck가 맞는지 검사 (현재 fd의 값이 올바른지 확인)
- remainder를 unsorted bin에 연결 (fd에 저장)
- remainder_size가 small bin크기면 fd_nextsize, bk_nextsize NULL로 초기화
- victim에 플래그 설정
- remainder에 PREV_INUSE비트 설정 및 remainder 다음 청크 prev_size 설정
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
victim 검사하고 p에 청크 주소 넣고 반환한다
binmap 탐색을 통해 재할당
이전에 fastbin, smallbin, unsorted bin 그리고 large bin까지 재할당 시도를 했다
하지만 해당 과정에서는 할당할 크기의 인덱스를 구해서 해당하는 bin만을 검사하여 재할당 시도를 하였다
해당 과정에서는 binmap을 통해 할당 가능한 freed chunk를 찾아서 재할당을 시도하게 된다
상세 과정은 아래 더보기
/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.
The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
사용된 값들을 변수에 저장한다
- idx 1 증가 (다음 크기 인덱스)
- bin: idx에 해당하는 bin주소 저장
- block: idx에 해당하는 block
- map : binmap[block]의 값을 저장
- bit : idx에 대한 비트 저장
for문이 돌아간다
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
}
while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
현재의 bit가 0이거나 map보다 bit가 클 경우에 수행된다 (자신의 block에는 할당받을 수 있는 청크가 없을 때)
do while문을 돌면서 자신보다 큰 블록에 free 된 청크가 있는지 확인한다
값이 존재하는 block이 있으면 bins의 주소를 구해 bin에 저장하고 bit은 1로 초기화한다
만약 block이 BINMAPSIZE의 범위를 넘어서면 use_top으로 점프해서 top청크를 통해 할당한다
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);
while문을 돌면서 찾은 block에서 존재하는 bin을 찾아낸다
그리고 victim에 찾은 bin의 bk를 저장한다
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}
else
{
...
}
if문에서 선택된 bin이 비워져 있는지 검사한다
비워져 있으면 map과 해당 binmap[block]에서 bit 값을 제거하고 for문 처음으로 다시 돌아간다
else문에서는 재할당한 청크를 검사 및 unlink등의 작업을 수행한다
size = chunksize (victim);
/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb;
/* unlink */
unlink (av, victim, bck, fwd);
초입 부분에서는 재할당 될 victim에 대한 코드가 수행된다
- size에 victim 청크의 사이즈 저장
- 할당 받을 사이즈(nb)와 같거나 큰지 비교
- remainder_size에 할당 받고 남은 청크의 사이즈 저장
- victim unlink 수행
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
...
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
remainder_size에 따라 if else문이 처리되고 victim을 검사하고 포인터 p에 넣고 반환한다
if문은 remainder_size가 최소 청크보다 작을 때 수행되는데 victim에 다음 청크에 inuse 비트를 설정하고
검사를 통해 NON_main_ARENA를 설정해준다
else문에서는 remainder 청크를 구해 unsorted bin에 넣어준다
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
- remainder에 할당 후 남은 청크의 주소 구해 저장
- bck에 unsored bin의 bins주소 저장
- fwd에 unsorted bin의 fd저장
- unsortd bin의 fd가 정상인지 검사 (fd->bck == unsorted bin주소여야함)
- remainder를 unsorted bin에 넣는다 (리스트에 연결)
- nb가 small bin 크기일 경우 remainder를 last_remainder에 저장한다
- nb가 largebin 범위일 경우 fd_nextsize, bk_nextsize를 NULL로 초기화한다
- victim, remainder의 플래그 설정
- remainder 다음 청크의 prev_size 설정
top chunk로 재할당
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).
We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/
victim = av->top;
size = chunksize (victim);
vitcim에는 top 청크의 주소 size에서는 top 청크 size를 저장
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
nb + 헤더가 top청크의 size와 같거나 작으면 수행되는 코드로 수행되는 동작을 아래와 같다
- top청크를 nb의 값을 뺀 주소로 갱신
- victim과 top 청크 size에 플래그와 size 설정
- victim 검사한 뒤 포인터 p에 victim에 할당할 힙의 주소 저장 후 반환
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
fastbin이 존재하면 fastbin chunk들을 병합시키고 nb에 해당하는 bin의 인덱스를 구해 idx에 저장한다
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
위 두 조건에 다 부합하지 않을 경우 sysmalloc으로 할당한다