1973년 11월에 나온 Unix 4판(V4) 소스에 malloc.c가 들어 있습니다. 그 안의 함수 이름은 malloc과 mfree입니다. free가 아니라 mfree이고, 해제할 때 크기까지 함께 넘깁니다.
취미 프로젝트를 하던 한 개발자가 이 원본 소스를 읽고 정리한 기록입니다. V4 소스가 담긴 테이프는 작년에 벨 연구소 창고를 정리하다 발견됐습니다. 그전까지 V4는 기록으로만 남아 있었고, 원문 제목이 the lost malloc()인 이유도 여기에 있습니다.
해제가 없어도 됐던 이유
C는 V4보다 한 해 앞선 2판에서 처음 나왔지만 그때는 많이 쓰이지 않았습니다. V4가 특별한 것은 커널 대부분이 이미 C로 작성돼 있던 시기라는 점입니다. 이 시대의 C 컴파일러는 하나의 프로그램이 아니라, 객체 파일을 얻기 위해 순서대로 연결하는 여러 도구의 묶음이었습니다.
그 도구 안에 원시적인 최적화기로 보이는 c20.c가 있고, 814행에 이런 호출이 있습니다. write(2, "Out of space\n", 14). 메모리를 얻는 루틴이 실패하면 메시지를 찍고 곧바로 프로세스를 끝냅니다.
이 루틴은 사실상 선형 할당기입니다. 크기를 정렬하고, 영역이 넉넉한지 보고, 모자라면 sbrk로 힙을 늘립니다. 실패하면 되돌아오지 않으니 호출부는 NULL을 검사하지 않습니다. 해제도 하지 않습니다. 모든 것이 프로그램이 끝날 때까지 살아 있는 구조였으니까요.

coremap과 swapmap, 영역을 넘기는 인터페이스
Dennis Ritchie의 디렉터리에 있는 malloc.c를 열면 함수 두 개가 나옵니다. malloc은 첫 인자로 맵을 받고, mfree는 맵과 크기와 포인터를 받습니다. 해제할 때 크기를 다시 넘기는, 지금보다 엄격한 형태입니다.
실제 호출은 이렇게 생겼습니다. p = malloc(coremap, sizeof foo). 그리고 mfree(coremap, sizeof foo, p). 스왑 영역을 쓰려면 같은 함수에 swapmap만 바꿔 넘기면 됩니다. 같은 코드가 두 종류의 메모리를 구분 없이 다룰 수 있는 구조입니다.
맵으로 넘기는 coremap과 swapmap은 그냥 평평한 메모리 영역입니다. 그래서 이 malloc은 특정 힙 하나를 쓰지 않고, 호출자가 지정한 영역 안에서만 움직입니다.
이 인터페이스가 주는 이점은 지금 기준으로도 낯설지 않습니다. 라이브러리에 영역 하나를 넘겨 두면, 그 영역을 통째로 버리는 것으로 라이브러리 메모리를 전부 회수할 수 있습니다. 힙 손상도 영역 단위로 갇히고, 서드파티 코드의 할당량에 상한을 걸기도 쉽습니다. 오늘날 아레나 할당과 같은 발상입니다.
다만 원문 저자는 이 장점들이 설계 의도는 아니었다고 봅니다. 이유는 하드웨어 제약이었을 가능성이 크다고 적었습니다. 실제로 이들은 영역 크기를 CMAPSIZ/2와 SMAPSIZ/2로 잡아야 했는데 잘못 잡았고, 나중에 고쳤습니다.
John Lions의 6판 주석서는 이 구조를 이렇게 설명합니다. 주기억장치는 32워드(64바이트) 단위, 디스크 스왑 영역은 256워드(512바이트) 단위로 관리합니다. 각 자원마다 사용 가능한 구역 목록을 맵에 두고, 어느 맵인지는 호출자가 넘깁니다.
할당 방식도 그때 이미 지금과 비슷합니다. 주소 순으로 정렬된 블록 목록에서 처음 맞는 자리를 고르고, 이웃 블록이 비면 합칩니다. arena라는 단어가 처음 등장하는 것도 6판 소스입니다.
V4의 선형 할당기와 달리 이쪽은 해제한 자리를 다시 씁니다. 처음 맞는 블록을 고르고 이웃과 합치는 방식은 오늘날 할당기의 기본 골격과 크게 다르지 않습니다.
매크로가 남긴 흔적
7판(1979)에는 ALLOC(x)라는 매크로가 있습니다. sizeof 계산을 매번 손으로 쓰지 않게 해 주는 형태입니다. 재미있는 것은 위치입니다. Fortran 77 컴파일러와 make의 헤더에 똑같이 들어 있는데, 두 프로그램 모두 Stuart Feldman이 작업했습니다. 같은 사람이 같은 불편을 두 번 해결한 흔적입니다.
8판(1985)에 오면 인터페이스가 지금과 비슷해집니다. 이 무렵 전통적인 malloc과 free로 넘어갔고, 할당을 0으로 초기화하는 tryalloc 같은 매크로도 생겼습니다. new(type) 형태의 매크로도 여기저기 보입니다.
9판에서는 Rob Pike가 작성한 sam 편집기 헤더에 new(t, n)과 gcnew가 있습니다. 7판에는 galloc과 gfree라는 할당기가 따로 있었는데, 도달할 수 없는 블록을 쓰레기로 보고 회수하는 물건이었습니다. 수동 관리가 편해진 뒤에도 사람들은 가비지 컬렉션을 원했습니다.

왜 지금의 malloc과 free가 됐나
원문은 malloc과 free의 조합이 처음에는 속기 쉽게 단순해 보인다고 적습니다. 객체 수명이 얽히기 시작하면 그 인상이 오래가지 않습니다.
원문은 이유를 세 갈래로 짚습니다. 프로그램이 다루는 객체의 수명이 대부분 단순했다는 점, 메모리가 작고 가상 메모리도 없어서 영역을 아껴 써야 했다는 점, 그리고 수동 관리 자체에 지쳐 있었다는 점입니다. 맵 인자를 없애면 호출부가 단순해지고, 구현 쪽이 똑똑해지면 됩니다.
C++의 new가 어디서 왔는지도 원문에서 따라갑니다. Stroustrup은 new 연산자를 Simula에서 빌려왔다고 적었습니다. malloc에 sizeof를 넣고 반환값을 검사하는 두 줄보다 new 한 줄이 실수하기 어렵다는 논리였습니다.
1992년 P. J. Plauger의 책에는 지금도 유효한 조언이 있습니다. 임의 순서로 할당하고 해제하면 반드시 쓸 수 없는 구멍이 남는다는 것입니다. 그래서 관련 있는 데이터를 한 구조로 모아 한 번에 할당하고, 수명이 비슷한 객체를 함께 해제하라고 권합니다. 원문 저자는 다만 그렇게 하도록 돕는 도구를 주지 않는 조언이라며 한 줄 덧붙입니다.
원문 반응
GeekNews 댓글은 new라는 이름의 기원을 더 파고듭니다. 원문이 SIMULA 67의 new를 찾아간 데서 멈추지 않고, C.A.R. Hoare가 1965년 11월에 발표한 Record Handling이 ALGOL 60 후속 언어를 위한 확장 제안이었고 큰 영향을 끼쳤다고 짚습니다.
SIMULA 67에서 더해진 기능 대부분이 이 논문에서 왔다는 설명입니다. 예외는 객체 지향의 핵심인 클래스 상속과 가상 프로시저이고, 이쪽은 Kristen Nygaard의 몫이라는 지적입니다. 한 이름의 유래를 따라가다 언어 설계자들의 이름까지 나오는 셈입니다.
실무로 가져올 것은 하나입니다. 수명이 같은 데이터는 한 영역에 모아 한 번에 해제하는 편이 안전합니다. 50년 전 커널이 쓰던 발상이 지금 라이브러리의 아레나 할당으로 남아 있습니다.
ASUS TUF Gaming 지포스 RTX 5080 OC D7 16GB 코잇
로켓배송으로 바로 받기이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.



