[CSAPP] malloc lab(2) 묵시적 메모리 할당기를 구현해보자
이 글은 malloc lab(1)에서 이어지는 내용이다.
묵시적 메모리 할당기 구현
이 글은 CSAPP에서 재시한 묵시적 메모리 할당기 구현법을 바탕으로 하고 있다.
해당 코드는 책의 코드를 해석하는 수준으로 공부하였기에 CSAPP와 거의 동일 한 코드이다.
매크로 정의
솔직히 말하자면 매크로를 이해하는 것이 이 챕터의 가장 큰 난관이다. 매크로만 제대로 이해한다면 다른 부분에서 로직이 어떻게 흘러가는 지 머릿속에 그려나갈 수 있을 것이다.
기본 정의
1
2
3
4
5
6
7
// 워드, 더블워드
# define WSIZE 4
# define DSIZE 8
// 힙 확장 기본 단위 - 1페이지 == 4KB
# define CHUNKSIZE (1 << 12) //4096 bytes
위의 코드는 딱히 설명할 필요는 없을 것 같다. 필요한 부분은 직접 찾아보길 바란다.
블록을 읽고 쓰는 매크로 함수들
PACK
이 부분은 아마 malloc lab(1)의 경계태그, 특히 “header” 챕터를 읽고 이해하였다면 쉽게 납득이 갈 거다.
1
2
// 헤더 푸터에 들어가는 값 - size와 할당 비트를 합쳐 1개의 워드에 저장
# define PACK(size, alloc) ((size) | (alloc))
블록의 크기(size)와 할당여부(alloc)를 받아서 하나의 수로 만들어주는 함수이다.
1
#define [이름] [값]
일단 # define은 전처리 지시문으로 컴파일 전 전처리 단계에서 이름 위치에 해당 값을 바꿔치기하라는 것이다.
1
#define 이름(a) ((a)+1)
함수형 매크로는 함수의 인자를 값에 대입해 계산 결과와 치환하라는 것이다. 이때 값은 괄호로 묶고 인자 값에서 사용하는 함수의 인자 또한 괄호로 묶어야 한다.
((x) | (y))
의 의미를 설명해주겠다.
|
는 비트 OR 연산자 이다.
OR 연산자는 다음과 같은 값을 반환한다.
1
2
3
4
0 | 0 -> 0
0 | 1 -> 1
1 | 0 -> 1
1 | 1 -> 1
이전글에서 말했듯이 8바이트 정렬이 맞춰져있기에 블록의 크기(size)는 비트 단위에서 하위 3자리는 사용하지 않는다. 그리고 우리는 할당정보(alloc)는 1/0으로 비트 최 하위 1자리에 표시 한다.
결과적으로 size와 alloc은 비트 단위에서 겹치지 않는 다는 것이다.
비트 단위에서 겹치지 않으면 문제(같은 자리에 1이 2개 있으면 1이 반환)가 발생하지 않기때문에 겹치지 않는 비트를 더할 수 있다. 이유는 실제 OR 연산은 숫자를 더하는 것이 아니라, 비트 단위로 겹치지 않게 정보를 담을 수 있도록 해주는 방식이기 때문이다. 해당 비트에 대한 yes or no를 표현했다라고 생각해도 좋다.
아래는 실제 블록의 정보가 비트 단위에서 어떻게 표현되는 지를 적어봤다.
1
2
3
4
11000 -> 24(size)
00001 -> 1(alloc)
11001 -> 할당된 24바이트크기의 블럭
GET, PUT
GET과 PUT은 핵심중에 핵심이다.
1
2
3
4
// 메모리 읽기
# define GET(p) (*(unsigned int *)(p))
// 메모리에 쓰기
# define PUT(p, val) (*(unsigned int *)(p) = (val))
GET은 주소 p
에 있는 4바이트 값을 읽는 다는 뜻이고 PUT은 주소 p
에 있는 4바이트 값을 val
로 써넣는다는 뜻이다.
unsigned int
는 부호 없는 4바이트 정수(양수)이다.
괄호가 많은 관계로 풀어서 한 번 보자.
일단 함수의 인자와 대치되는 p
, val
은 그냥 치환된다고 생각하면 쉽다.
그리고 unsigned int *
는 형변환을 하는 것이다. 형변환의 이유는 대부분 p
가 void로 선언되어 다른 형태로 쉽게 변환되어 사용된 후 실제 주소로 사용될 때는 음수 처리가 되지않는 unsigned int로 받아야 하기 떄문이다. 음수 처리 때문에 그냥 int
를 사용하면 주소가 꼬일 수도 있다.
1
2
int x = 0xffffffff -> -1
unsigned int y = 0xffffffff -> 엄청 큰 수(모름)
그리고 값을 나타내는 부분이기에 전체를 한 번 더 괄호로 묶어주었다.
(*(unsigned int *)(p) = (val))
를 풀어서 보면 인자로 받은 p를 부호가 없는 4바트 정수형으로 형변환 하여 그 주소의 값을 가져와서 val을 써 넣는 다라고 해석 할 수 있다.
GET_SIZE, GET_ALLOC
1
2
3
4
5
// 헤더 or 푸터 읽고 사이즈 추출하기
# define GET_SIZE(p) (GET(p) & ~0x7)
// 헤더 or 푸터 읽고 주소 추출하기
# define GET_ALLOC(p) (GET(p) & 0x1)