프로그램이 실행되면 운영체제는 메모리 (RAM) 에 이 프로그램을 위한 공간을 할당해준다.
프로그램을 위한 공간은 4가지로 나누어져 있는데, 이는 코드 (Code) / 데이터 (Data) / 힙 (Heap) / 스택 (Stack) 이다.
Code
우리가 작성한 소스 코드가 기계어 (컴퓨터가 읽을 수 있는 가장 밑단의 언어이며, 0과 1로만 이루어져 있다.) 형태로 저장되어 있는 영역
컴파일 타임 때 크기가 결정된다.
Data
전역 변수, static 변수가 저장되어 있는 영역
프로그램 시작과 동시에 할당되고, 프로그램이 종료되어야 메모리가 해제된다.
실행 도중 변수 값이 변경될 수 있기 때문에 Read-Write로 지정된다.
컴파일 타임 때 크기가 결정된다.
Heap
프로그래머가 할당 / 해제하는 메모리 영역
프로그래머는 malloc, calloc 으로 힙에 메모리를 할당할 수 있으며, 이를 동적 할당이라고 한다. (Swift에서는 malloc, calloc 함수를 써서 직접 할당할 일이 별로 없긴 하지만, 클래스 인스턴스나 클로저 같은 참조 타입의 값은 모두 힙에 자동 할당된다.)
사용하고 난 후에 메모리 해제를 해주지 않으면 Memory Leak (메모리 누수) 가 발생하기 때문에 반드시 사용하고 난 후에는 메모리 해제를 해주어야 한다. (Swift에서는 ARC를 통해 힙에 할당된 메모리가 더 이상 쓸모 없어지면 자동으로 해제해주기 때문에 굳이 프로그래머가 직접 해제를 해줄 필요가 없다.)
메모리의 4가지 영역 중 유일하게 런타임 때 크기가 결정되기 때문에 데이터의 크기가 확실하지 않을 때 사용한다.
메모리 크기에 제한이 없다.
본질적인 범위가 전역이기 때문에 프로그램의 모든 함수에서 접근 가능하다.
할당 / 해제 작업 & 힙 손상 (이중 해제, 해제 후 사용 등) 작업 & 힙 경합 (2개 이상 쓰레드가 동시에 접근하려고 할 때 걸리는 Lock) 때문에 속도가 저하된다. (엄청 느리다는 것은 아니고, 스택보다 상대적으로 느리다는 뜻)
메모리를 직접 관리해야 한다. (메모리 해제와 비슷한 맥락)
Stack
함수 호출할 때 함수의 지역 변수, 매개 변수, 리턴 값 등이 저장되는 영역
함수가 종료되면 저장된 메모리도 해제 (반환) 된다.
컴파일 타임에 결정되기 때문에 무한히 할당할 수 없다. (메모리 크기에 제한이 있다.)
LIFO (Last In, First Out) 구조이기 때문에 늦게 저장된 변수일수록 빠르게 해제된다.
CPU가 스택 메모리를 효율적으로 구성하기 때문에 (CPU에 의해 관리되고 최적화되기 때문에) 속도가 매우 빠르다.
메모리를 직접 해제해주지 않아도 된다.
지역 변수에만 접근이 가능하다.
컴파일 타임 때 크기가 결정된다.
Heap / Stack 적합한 사용
스택은 메모리가 한정되어 있기 때문에 데이터의 크기를 모르거나 스택에 저장하기엔 큰 데이터인 경우에는 힙에 할당하고, 그외 나머지는 스택에 할당하면 된다. (인스턴스나 클로저와 같이 자동으로 힙에 할당되는 것은 어쩔 수 없다.)
Heap / Stack 메모리 관계
힙과 스택은 같은 메모리 영역을 공유한다.
힙은 낮은 메모리 주소 (0x00000000) 부터 할당받고, 스택은 높은 메모리 주소 (0xffffffff) 부터 할당받는다.
Stack Overflow
스택에 너무 많은 메모리를 할당해서 자신의 스택 영역을 초과한 경우
쉽게 말해, 스택이 범람했다는 뜻
iOS에서는 스택 오버 플로우가 발생하면 앱이 죽기 때문에 조심해야 한다. (ex. 무한 재귀)