1) 블록 할당
사용 여부
와 블록의 사이즈
를 각 블록 내부의 header 및 footer에 저장한다.크기가 맞는 첫번째 가용 블록
을 선택한다. (First fit)나머지는 분할
하여 또다른 가용 블록으로 남겨둔다.2) 블록 반환
/* 기본 상수 */
#define WSIZE 4
#define DSIZE 8
#define CHUNKSIZE (1 << 12)
/* 매크로 */
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define PACK(size, alloc) ((size) | (alloc))
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE)))
#define WSIZE 4
#define DSIZE 8
#define CHUNKSIZE (1 << 12)
#define MAX(x, y) ((x)>(y)? (x): (y))
PACK
GET
PUT
GET_SIZE
GET_ALLOC
HDRP
/ FTRP
NEXT_BLKP
/ PREV_BLKP
mm_init()
int
: 성공 시 0, 실패 시 -1↳ 내부 함수(2) : extend_heap(size)
size
: 확장하고자 하는 힙 사이즈 (words)void*
확장하며 생긴 가용 블록의 포인터👩🏻💻 source code
int mm_init(void)
{
char *heap_listp;
if ((heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1)
return -1;
PUT(heap_listp, 0);
PUT(heap_listp + (1 * WSIZE), PACK(DSIZE, 1));
PUT(heap_listp + (2 * WSIZE), PACK(DSIZE, 1));
PUT(heap_listp + (3 * WSIZE), PACK(0, 1));
heap_listp+= (2*WSIZE);
if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
return -1;
return 0;
}
초기에 ‘정렬 패딩(unused) + 프롤로그 header + 프롤로그 footer + 에필로그 header’를 담기 위해 4워드 크기의 힙을 생성한다.
생성 후 CHUNKSIZE만큼 추가 메모리를 요청해 힙의 크기를 확장한다.
순서 확인하기
mem_sbrk
)💡 미사용 패딩을 사용하는 이유
static void *extend_heap(size_t words)
{
char *bp;
size_t size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE;
if ((long)(bp = mem_sbrk(size)) == -1)
return NULL;
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));
return coalesce(bp);
}
mm_malloc
이 적합한 fit 공간을 찾지 못했을mem_sbrk
또한 더블 워드로 정렬된 메모리 블록을 리턴한다.int
: 성공 시 0, 실패 시 -1static void *coalesce(void *bp)
{
size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
size_t size = GET_SIZE(HDRP(bp));
// @pb : 이전 블록, @cb : 현재 블록, @nb : 다음 블록
// [CASE 1] : pb, nb - 둘 다 할당 상태
if (prev_alloc && next_alloc)
{
return bp;
}
// [CASE 2] : pb - 할당 상태 / nb - 가용 상태
// resize block size : cb + nb
else if (prev_alloc && !next_alloc)
{
size += GET_SIZE(HDRP(NEXT_BLKP(bp))); // 현재 블록 사이즈에 다음 블록 사이즈 더함
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
}
// [CASE 3] : pb - 가용 상태 / nb - 할당 상태
// resize block size : pb + cb
else if (!prev_alloc && next_alloc)
{
size += GET_SIZE(HDRP(PREV_BLKP(bp)));
PUT(FTRP(bp), PACK(size, 0));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
bp = PREV_BLKP(bp);
}
// [CASE 4] : pb - 가용 상태 / nb - 가용 상태
// resize block size : pb + nb
else {
size += GET_SIZE(HDRP(PREV_BLKP(bp))) +
GET_SIZE(FTRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
bp = PREV_BLKP(bp);
}
return bp;
}
coalesce
함수에서는 현재 블록이 살필 수 있는 4가지 케이스를 다뤄야 한다.
💡 bp는 항상 블록의 헤더 뒤에 위치하는게 좋기 때문에 연결이 끝나면 bp는 블록의 헤더에 위치해야 한다.
void*
: 반환하려는 블록의 포인터void mm_free(void *bp){
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp),PACK(size,0));
PUT(FTRP(bp), PACK(size,0));
coalesce(bp);
}
coalesce(bp)
를 호출하여 인접 가용 블록을 연결한다.💡핵심은 블록을 가용 리스트에서 제거하는게 아니라 가용상태로 만들어버린다는 것
mm_malloc(size)
int
: 성공 시 0, 실패 시 -1void *mm_malloc(size_t size)
{
int newsize = ALIGN(size + SIZE_T_SIZE);
void *p = mem_sbrk(newsize);
if (p == (void *)-1)
return NULL;
else {
*(size_t *)p = size;
return (void *)((char *)p + SIZE_T_SIZE);
}
}
void *mm_malloc(size_t size)
{
size_t asize;
size_t extendsize;
char *bp;
if (size == 0)
return NULL;
if (size <= DSIZE)
asize = 2 * DSIZE;
else
asize = DSIZE * ((size + (DSIZE) + (DSIZE - 1)) / DSIZE);
bp = next_fit(asize); // Choice fit-method : first_fit, next_fit, best_fit
if (bp != NULL) {
place(bp, asize);
next_heap_listp = bp;
return bp;
}
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
return NULL;
place(bp, asize);
next_heap_listp = bp;
return bp;
}
size
를 조정한다. → 블록의 크기는 적어도 16바이트 이상이어야 한다. (header 4bytes + footer 4bytes + payload 8bytes) → 블록의 크기는 8의 배수이어야 하므로(정렬 요건), 항상 8의 배수가 되도록 올림 처리한다.find_fit
) → 맞는 블록을 찾으면 요청한 블록을 배치한다. (place
) 필요하면 기존 블록을 분할한다. → 맞는 블록을 찾지 못하면 힙을 늘리고 다시 배치한다.find_fit(size)
size
: 필요한 가용 블록의 사이즈 (byte)void*
가용 블록의 포인터💡 가용 블록 탐색 방식
first fit: 가용블럭 리스트를 처음부터 검색해서 크기가 맞는 첫번째 가용 블록을 선택
next fit: 검색을 리스트의 처음에서 시작하는 대신, 이전 검색이 종료된 지점에서 검색을 시작
best fit: 모든 가용블록을 검새해서 크기가 가장 맞는 가장 작은 블록을 선택
bp = heap_listp
: bp
변수를 가용 블록 리스트의 시작인 heap_listp
로 초기화GET_SIZE(HDRP(bp)) > 0
: 현재 가용 블록의 크기가 0보다 큰지를 확인. 이 조건은 힙의 끝을 나타내는 에필로그 블록에 도달하기 전까지 루프를 실행bp = NEXT_BLKP(bp)
: 다음 가용 블록으로 이동. NEXT_BLKP
매크로를 사용하여 현재 블록의 다음 블록 주소로 이동!GET_ALLOC(HDRP(bp))
: 현재 블록의 헤더가 가리키는 블록이 할당되지 않았는지 확인(asize <= GET_SIZE(HDRP(bp)))
: 요청한 크기(asize
)가 현재 블록의 크기보다 작거나 같은지 확인NULL
을 반환static void *next_fit(size_t asize)
{
char *bp;
for (bp = NEXT_BLKP(next_heap_listp); GET_SIZE(HDRP(bp)) > 0; bp = NEXT_BLKP(bp))
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp))))
return bp;
for (bp = heap_listp; bp <= next_heap_listp; bp = NEXT_BLKP(bp))
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp))))
return bp;
return NULL;
}
bp = NEXT_BLKP(next_heap_listp):
bp
변수를 다음 블록의 시작인 next_heap_listp
로 초기화GET_SIZE(HDRP(bp)) > 0
: 현재 가용 블록의 크기가 0보다 큰지를 확인. 이 조건은 힙의 끝을 나타내는 에필로그 블록에 도달하기 전까지 루프를 실행bp = NEXT_BLKP(bp)
: 다음 가용 블록으로 이동. NEXT_BLKP
매크로를 사용하여 현재 블록의 다음 블록 주소로 이동!GET_ALLOC(HDRP(bp))
: 현재 블록의 헤더가 가리키는 블록이 할당되지 않았는지 확인(asize <= GET_SIZE(HDRP(bp)))
: 요청한 크기(asize
)가 현재 블록의 크기보다 작거나 같은지 확인NULL
을 반환💡 왜 bp 선언의 자료형이 다를까?
first_fit
함수는 임의의 데이터 타입을 가리킬 수 있는 포인터를 반환하고, next_fit
함수는 다음 블록을 가리키는 포인터로 바이트 단위로 이동할 수 있도록 char *
형 포인터를 사용한다.
static void *best_fit(size_t asize)
{
void *bp;
void *best_fit = NULL;
for (bp = heap_listp; GET_SIZE(HDRP(bp)) > 0; bp = NEXT_BLKP(bp))
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp))))
if (!best_fit || GET_SIZE(HDRP(bp)) < GET_SIZE(HDRP(best_fit)))
best_fit = bp;
return best_fit;
}
best_fit이 아직 설정되지 않았거나 (GET_SIZE(HDRP(bp)) < GET_SIZE(HDRP(best_fit)))
, 현재 블록이 best_fit보다 더 작은 경우, best_fit을 현재 블록으로 업데이트한다.
extend_heap(size)
size
: 확장하고자 하는 힙 사이즈 (words)place(bp, size)
bp
: 찾은 가용 블록의 포인터size
: 블록 할당에 필요한 사이즈 (byte)static void place(void *bp, size_t asize)
{
size_t csize = GET_SIZE(HDRP(bp));
if ((csize - asize) >= (2 * DSIZE)) {
PUT(HDRP(bp), PACK(asize, 1));
PUT(FTRP(bp), PACK(asize, 1));
bp = NEXT_BLKP(bp);
next_heap_listp = bp;
PUT(HDRP(bp), PACK(csize - asize, 0));
PUT(FTRP(bp), PACK(csize - asize, 0));
} else {
PUT(HDRP(bp), PACK(csize, 1));
PUT(FTRP(bp), PACK(csize, 1));
next_heap_listp = NEXT_BLKP(bp);
}
}
bp
: 재조정하려는 공간의 포인터size
: 재조정하려는 사이즈void*
: 재조정한 메모리의 포인터void *mm_realloc(void *ptr, size_t size) {
if (ptr == NULL) {
return mm_malloc(size);
}
if (size == 0) {
mm_free(ptr);
return NULL;
}
size_t old_size = GET_SIZE(HDRP(ptr)) - DSIZE;
size_t copy_size = old_size < size ? old_size : size;
void *new_ptr = mm_malloc(size);
if (new_ptr == NULL)
return NULL;
memcpy(new_ptr, ptr, copy_size);
mm_free(ptr);
return new_ptr;
}
예외 처리
ptr
이 NULL인 경우에는 할당만 수행한다. (mm_malloc
함수 호출)size
가 0인 경우에는 메모리 반환만 수행한다. (mm_free
함수 호출)새 블록에 할당
size
만큼 할당할 수 있는 블록을 찾아 할당한다. (mm_malloc
함수 호출)데이터 복사
ptr
이 가리키고 있던 블록이 가지고 있는 payload 크기를 구하고,새로 할당할 size
보다 기존의 크기가 크다면 size로 크기를 조정한다.size
만큼 새로 할당한 블록으로 데이터를 복사한다. (memcpy
함수 호출)기존 블록 반환
mm_free
함수 호출)[c언어] Malloc Lab :: 동적 할당기 구현하기 (Implicit List, Explicit List, Segregated List, Buddy System)