Post

[CSAPP] malloc lab(2) 묵시적 메모리 할당기를 구현해보자

[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

GETPUT은 핵심중에 핵심이다.

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 *는 형변환을 하는 것이다. 형변환의 이유는 대부분 pvoid로 선언되어 다른 형태로 쉽게 변환되어 사용된 후 실제 주소로 사용될 때는 음수 처리가 되지않는 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)

This post is licensed under CC BY 4.0 by the author.