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)