

계속 정글에서 공부한 내용을 블로그로 정리하며 느껴지는 점은,
과제의 난이도가 높아지고 분량이 길어질수록 코드를 일일이 블로그에 설명할 순 없다는 점입니다.
그래서 노선을 바꿔서, 이제부턴 모든 코드를 일일이 설명하기보단 중요한 부분만 개념과 엮어서 설명해 보려고 합니다. 그리고 완벽히 정리해야 한다는 생각을 버리고 좀 두서없이 정리해 보겠습니다. 대신 코드는 테스트케이스를 통과했으니 두서 있을(?) 거에요.
이번 글엔 묵시적 리스트를 통해 Malloc 함수를 구현한 과정을 정리해 봤습니다.
매크로 함수 등 설정은 CSAPP 책 9.9.12절에 잘 나와 있습니다.
malloc 함수는 가용 리스트를 살펴보며, 어떤 빈 블록에 메모리를 할당할지 결정합니다.
0x100에 있다면, 헤더는 4바이트이므로 블록의 시작주소는 0x104입니다.(블록의 크기 / 할당 여부)가 저장됩니다.mm_init: 힙 초기화
힙을 초기화했을 때, 묵시적 가용 리스트는 다음과 같이 16바이트로 구성됩니다.
프롤로그 블록을 가리키는 전역 포인터 static char *heap_listp를 사용하게 됩니다.
힙을 초기화한 이후에는 실제 데이터를 할당할 수 있게, 힙을 확장해야 합니다.
// 힙을 초기화. 성공하면 0, 실패하면 -1 반환.
int mm_init(void)
{
// mem_init()은 해줄 필요 없음. 이미 mdriver.c 파일에서 자동 실행됨.
// WSIZE: 4바이트, DSIZE: 8바이트
// (1) 메모리 시스템에서 4워드(16바이트)를 가져와서, 빈 가용 리스트를 만듦
heap_listp = (char *)mem_sbrk(4 * WSIZE); // 할당된 메모리의 시작주소
if (heap_listp == (void *) -1) return -1; // 메모리 할당에 실패했을 때
// (2) 16바이트를 채워야 함
PUT(heap_listp, 0); // (A) 맨 처음 비어 있는 4바이트
PUT(heap_listp + WSIZE, PACK(DSIZE, 1)); // (B) 프롤로그 블록의 헤더 (8 / 1)
PUT(heap_listp + WSIZE * 2, PACK(DSIZE, 1)); // (C) 프롤로그 블록의 푸터 (8 / 1)
PUT(heap_listp + WSIZE * 3, PACK(0, 1)); // (D) 에필로그 블록의 헤더 (실제론 4바이트, 표시는 (0 / 1))
// (3) 가용 블록을 생성??
heap_listp += WSIZE * 2; // 프롤로그 블록의 헤더 직후 위치
// 힙 확장하기 (CHUNKSIZE: 4096바이트)
if (extend_heap(CHUNKSIZE / WSIZE) == NULL)
return -1;
return 0;
}
extend_heap: 힙 확장하기
// 힙이 초기화될 때 OR mm_malloc이 fit을 찾지 못했을 때, 힙을 요청크기(words)만큼 확장한다. 이후 새롭게 생긴 블록의 주소를 반환한다.
// words는 워드 단위로 입력받으며, 1워드 = 4바이트 이다.
static void *extend_heap(size_t words){
size_t size = ALIGN(words * WSIZE);
char *bp;
// mem_sbrk(size)`는 힙 영역을 `size`만큼 늘려주고, 연장된 영역의 시작 주소를 반환
bp = mem_sbrk(size); // 새로 할당된 블록의 포인터
if (bp == (void *) -1) return NULL; // 메모리 할당에 실패했을 때
// 이전 에필로그 블록 자리에, 새로운 헤더가 옴
PUT(HDRP(bp), PACK(size, 0)); // 헤더
// 새로운 푸터 및 에필로그 블록
PUT(FTRP(bp), PACK(size, 0)); // 푸터
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); // 에필로그 블록
// Coalesce 함수로 병합
return coalesce(bp);
}
mm_free: 블록 반환하기// ptr가 가리키는 블록에 할당된 메모리를 반환한다. 반환값은 없다.
void mm_free(void *ptr)
{
// 그러면 해당 주소의 할당 비트는 1 -> 0이 되지 않을까?
size_t size = GET_SIZE(HDRP(ptr));
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));
// 인접 가용 블록을 통합
coalesce(ptr);
}
coalesce: 블록 병합하기
// ptr이 가리키는 블록 전후에 가용 블록이 있으면 통합한다.
// 통합된 블록의 시작 주소를 반환한다.
static void *coalesce(void *ptr){
size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(ptr))); // 이전블록 할당비트
size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(ptr))); // 다음블록 할당비트
size_t size = GET_SIZE(HDRP(ptr));
// 1(할당)이면 참, 0(비었음)이면 거짓... 기본이죠?
if (prev_alloc && next_alloc){
// 연결 불가능. 넘어가기.
} else if (prev_alloc) {
// 비어 있는 다음 블록과 연결 가능.
size += GET_SIZE(HDRP(NEXT_BLKP(ptr))); // 다음 블록의 크기 더하기
PUT(HDRP(ptr), PACK(size, 0)); // 현재 블록의 헤더 수정
PUT(FTRP(ptr), PACK(size, 0)); // 다음 블록의 푸터 수정
// 이때 FTRP(NEXT_BLKP(ptr))이 아니라 그냥 FTRP(ptr) 해 줘야 합니다
// 헤더의 크기정보를 수정하면서 두 블록이 이미 통합된 상황이라 그렇습니다
} else if (next_alloc) {
// 이전 블록과 연결 가능.
size += GET_SIZE(HDRP(PREV_BLKP(ptr))); // 이전 블록의 크기 더하기
PUT(FTRP(ptr), PACK(size, 0)); // 현재 블록의 푸터 수정
PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0)); // 이전 블록의 헤더 수정
ptr = PREV_BLKP(ptr); // 통합된 블록으로 주소 갱신
} else {
// 이전, 다음 블록과 연결 가능.
size += GET_SIZE(HDRP(PREV_BLKP(ptr))); // 이전 블록의 크기 더하기
size += GET_SIZE(FTRP(NEXT_BLKP(ptr))); // 다음 블록의 크기 더하기
PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0)); // 이전 블록의 헤더 수정
PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 0)); // 다음 블록의 푸터 수정
ptr = PREV_BLKP(ptr); // 통합된 블록으로 주소 갱신
}
return ptr; // 통합된 블록의 포인터 반환
}
mm_malloc: 블록 할당하기size만 할당하라고 요청해도, 실제로는 거기에 헤더 4바이트, 푸터 4바이트까지 포함하여 할당이 됩니다.
// size 바이트를 동적 할당해 달라고 요청한다.
// 할당된 블록의 시작 주소를 반환한다.
void *mm_malloc(size_t size)
{
size_t asize; // 실제로 할당되는 블록의 크기
size_t extendsize; // fit이 없는 경우 이만큼 힙을 연장
char *bp;
// size == 0인 경우 NULL을 반환
if (size == 0) return NULL;
// 현재 size에 헤더 + 푸터를 포함하고, 인접한 DSIZE(8바이트)의 배수로 올림 (패딩)
asize = ALIGN(size + DSIZE);
// 할당할 블록 정하기 -> first fit 사용
bp = find_fit(asize);
if (bp != NULL){
place(bp, asize); // 주소 bp에서 asize만큼 할당
return bp; // 할당된 블록 주소 반환
}
// 맞는 칸이 없으면 힙을 연장
extendsize = MAX(asize, CHUNKSIZE);
bp = extend_heap(extendsize / WSIZE);
if (bp == NULL){
// 힙 연장에 실패한 경우
return NULL;
} else {
// 새로 연장하면서 생긴 영역에 할당
place(bp, asize);
return bp;
}
}
find_fit: 할당할 블록 찾기 구현 (First Fit)할당해야 하는 크기 <= 블록의 크기이고 (이때 할당해야 하는 크기는 헤더 + 푸터 + 패딩비트를 포함한 크기입니다. 아래 코드 기준 asize임.)할당비트 == 0일 때 (즉 빈 블록일 때)// 묵시적 가용 리스트에서 first fit 검색을 수행.
// 크기 asize를 저장할 수 있는 첫 블록의 주소를 반환. 찾지 못하면 NULL 반환.
static void *find_fit(size_t asize){
void *bp; // 현재 검색중인 블록
size_t block_size; // 현재 블록의 크기
// 에필로그 블록 도달 시 종료
for (bp = heap_listp; (block_size = GET_SIZE(HDRP(bp))) != 0; bp = NEXT_BLKP(bp)){
// 사이즈에 맞는 가용 블록이 있는 경우, 해당 블록 주소를 반환
if (asize <= block_size && GET_ALLOC(HDRP(bp)) == 0){
return bp;
}
}
return NULL;
}
place: 블록 할당하고 분할하기
asize) 및 나머지 부분의 크기 (아래 함수 기준 rest_size)를 계산해야 합니다.나머지 부분의 크기 >= 블록의 최소 크기인 경우, 나머지 부분을 별도의 빈 블록으로 분리해 나중에 할당될 수 있게 합니다.나머지 부분의 크기 < 블록의 최소 크기인 경우, 블록의 분할 없이 할당합니다.// 주소 bp의 블록에 asize만큼 메모리를 할당
// 할당된 블록의 주소를 반환
static void place(void *bp, size_t asize){
size_t curr_size, rest_size;
// 나머지 부분의 크기를 계산한다
curr_size = GET_SIZE(HDRP(bp));
rest_size = curr_size - asize;
if (rest_size >= 4 * WSIZE){
// 최소 블록 크기 (4 * 4바이트 = 16바이트) 이상인 경우 분할을 수행한다
// 앞 블록의 헤더
PUT(HDRP(bp), PACK(asize, 1));
// 앞 블록의 푸터
PUT(FTRP(bp), PACK(asize, 1));
// 뒷 헤더 만들기
PUT(HDRP(NEXT_BLKP(bp)), PACK(rest_size, 0));
// 뒷 블록의 푸터 만들기
PUT(FTRP(NEXT_BLKP(bp)), PACK(rest_size, 0));
} else {
// 최소 블록 크기 이상이 아닌 경우, 분할을 수행하지 않는다
// 헤더, 푸터만 0에서 1로 바꾸기
PUT(HDRP(bp), PACK(curr_size, 1));
PUT(FTRP(bp), PACK(curr_size, 1));
}
}
mm_realloc: 할당된 메모리의 크기를 변경하기mm_malloc / mm_free 함수를 재사용해서 구현했습니다.ptr과, 변경할 크기 size를 매개변수로 받게 됩니다. 단, 두가지 예외 경우를 고려합시다.mm_malloc(size)와 동일하게 단순히 size 크기만큼 메모리를 할당하고, 블록의 시작 주소를 반환합니다.mm_free(ptr)와 동일하게 해당 블록에 할당된 메모리를 반환합니다.mm_malloc(size)로, 변경할 크기만큼의 메모리 공간을 할당받습니다.(기존 블록의 크기 < 새로 할당된 크기)인 경우, 기존 블록 전체가 복사됩니다. (기존 블록의 크기 > 새로 할당된 크기)인 경우, 기존 블록 중 새로 할당된 크기에 해당되는 앞부분만 복사됩니다.기존 블록의 크기 (헤더로 확인)와 새로 할당된 크기 (size 매개변수) 중 최솟값만큼 복사를 하면 되겠지요.mm_free로 기존 블록의 메모리 할당을 해제하고, 새롭게 할당받은 포인터를 반환하면 되겠습니다.// ptr가 가리키는 블록에 할당된 메모리의 크기를 size로 변경
void *mm_realloc(void *ptr, size_t size)
if (ptr == NULL){
// ptr가 NULL -> mm_malloc(size)와 동일.
return mm_malloc(size);
} else if (size == 0){
// size가 0 -> mm_free(ptr)과 동일.
mm_free(ptr);
return NULL;
}
// 현재 블록을 가리키는 포인터
void *oldPtr = ptr;
// 메모리 크기를 변경한 후, 블록을 가리키는 포인터
void *newPtr = mm_malloc(size);
// 이전 블록에서 복사해야 하는 데이터의 크기
// (이전 블록의 크기), (새로운 블록의 크기) 중 더 작은 값
size_t copySize = GET_SIZE(HDRP(ptr));
if (size < copySize){
copySize = size;
}
// 이전 블록의 내용을 새 블록으로 복사
memcpy(newPtr, oldPtr, copySize);
// 이전 블록에 할당된 메모리 free
mm_free(oldPtr);
return newPtr;
}

정글에서 준 test case로 채점을 해 보면 100점 만점에 62점이 나옵니다.
util 점수: 메모리를 얼마나 효율적으로 사용했는지thru 점수: 1초에 몇 천개의 연산을 처리했는지
안녕하세요!! 이제 7주차 말록구현하고있는 10기입니다!!
선배님께 궁금한게 있는데
혹시 이거 직접 구현 하신건가요?
realloc 같은 경우
저는 도저히 어떻게 해야할지 모르겠어서 따라치면서 이해해보려고 하는데 괜찮을까요 ㅠㅠ..