C에서 메모리를 할당할 때 사용하는 malloc 함수는 표준함수이기는 하지만 시스템콜은 아니다. 사실 malloc은 내부적으로 시스템콜 함수인 brk/sbrk와 mmap을 사용하게 된다.
brk와 mmap에 대한 내용이 복잡하기 때문에 추상적으로나마 이해해보고 이중 free를 하면 안 되는 이유를 알아보자.
brk()와 sbrk()는 사용할 데이터 영역의 끝 부분을 메모리를 증가시켜나거나 줄이는 시스템콜이다.
brk는 시스템에 메모리가 충분할 때 프로그램의 데이터 영역인 데이터 세그먼트의 끝(break)을 원하는 주소 값으로 설정한다.
sbrk는 데이터 세그먼트를 일정 바이트만큼씩 증가/감소시킨다. 또 sbrk는 현재 break의 위치를 찾을 때도 쓰인다.
이처럼 끝에서 쌓아 올려가며 메모리를 할당하는 방식 때문에 동적 메모리 할당을 heap이라고 부르는 것이다.
전통적으로 사용되던 brk 방식 이후 등장한 mmap()은 brk/sbrk처럼 데이터 세그먼트의 끝을 조절하는 것이 아니라 여기저기 흩어져 있는 메모리를 할당해주는 시스템콜이다.
malloc은 brk 방식과 mmap 방식을 적당히 섞어서 사용하는데, 작은 메모리 블록이 필요할경우엔 brk, 큰 블록이 필요할 경우엔 mmap 방식을 활용한다.
malloc은 동적 할당을 할 것을 요청받으면 먼저 이전에 free()를 통해 해제된 메모리 블록 리스트를 확인해본다.
요구된 메모리 블록을 찾으면 호출자에게 리턴하고 나머지는 free list에 남겨둔다.
만약 요구된 메모리가 free list에 있는 것보다 크면 sbrk나 mmap을 호출해 메모리를 추가로 할당한다.
다만 sbrk()의 경우 작은 단위로 메모리를 할당하기 때문에 성능을 위해 호출 횟수를 줄여 오버헤드를 줄일 필요가 있다. 따라서 필요한 크기보다 더 큰 단위로 프로그램의 break를 증가시키고, 남은 메모리는 free list에 남겨 둔다.
한편 free() 함수는 메모리 블록을 별도의 free list에 기록한다. 또 malloc이 메모리 블록을 할당할 때 추가 바이트를 할당해 블록의 크기를 기록해 둔다.
이런 과정이 반복되면 malloc이 관리하는 메모리 영역과 free가 관리하는 메모리 영역이 뒤섞이게 된다.
이렇게 free도 malloc도 자체 free list를 관리하게 되면서 이중 free를 하면 free list가 꼬이게 돼 오류가 발생하게 되는 것이다.