Date: February 2, 2025 Multi-select: Memory
스택 메모리
LIFO 구조로, 함수 호출 시 생성되는 스택 프레임(로컬 변수, 파라미터, 반환 주소 등)을 저장한다. 함수가 호출 될 때 마다 새로운 스택 프레임이 쌓이고, 함수가 종료되면 스택 프레임 제거된다.
특징
- 빠른 메모리 할당/해제: 스택은 메모리가 자동으로 관리되어서 함수가 끝나면 알아서 메모리가 반환됨
- 고정 크기: 스택 메모리는 크기가 제한적이라서 너무 많은 데이터를 저장하면 스택 오버플로우 위험이 있음
- 호출 상태 저장: 함수의 매개변수와 지역 값 일부가 스택 프레임에 저장될 수 있음. 실제 저장 위치는 ABI와 컴파일러 최적화에 따라 레지스터나 다른 영역이 될 수도 있음
- 스레드별 스택: 각 스레드는 별도의 호출 스택을 가지지만, 이것만으로 함수나 데이터 전체가 thread-safe해지는 것은 아님. 스택에 있는 참조가 공유 객체를 가리킬 수도 있음
스택 오버플로우
프로그램이 스택 메모리를 초과할 때 발생하는 오류, 함수 호출 시마다 생성되는 스택 프레임이 쌓이면서 스택 공간을 사용하게 되고, 이 제한된 스택 공간을 초과해서 스택 프레임이 쌓일 경우 발생한다.
너무 깊은 재귀 호출이나 대용량 지역 변수를 선언했을 때 스택 오버플로우 문제가 발생한다.
스택 오버플로우를 방지하는 방법은 다음과 같다:
- 재귀 호출 대신 반복문 사용하기
- 큰 데이터는 힙에 저장하기
스택 버퍼 오버플로우
스택에 할당된 메모리 버퍼(고정 크기)의 경계를 초과하여 데이터를 쓰는 상황, 메모리 손상, 예기치 않은 동작, 악의적인 코드 실행을 동반할 수 있음
- 스택 버퍼: 스택 메모리에 할당된 고정 크기의 메모리 공간 함수 내부에서 선언된 고정 크기 배열이나 데이터 블록이 스택 메모리에 저장됨
스택 버퍼 오버플로우를 방지하는 방법은 다음과 같다:
- 스택 카나리: 함수 호출 시 스택 프레임에 특수한 값을 두고, 함수가 반환되기 전에 값이 변경됐는지 검사하는 방어 기법
- 언어 차원의 메모리 안정성
- 배열 경계 경사
- 옵셔널로 널 포인터 참조 방지
- 타입 안정성
힙
동적 메모리 할당에 사용되는 영역, 데이터의 수명과 관리 방식이 다르기 때문에 앱 성능과 메모리 관리에 중요한 역할을 한다.
특징
- 동적 할당
- 런타임동안 필요에 따라 메모리를 유연하게 할당하고 해제
- 수명 관리
- Swift 클래스 인스턴스는 주로 힙에 저장되며 ARC가 참조 수를 바탕으로 수명을 관리한다. 실제 메모리 할당과 해제는 런타임과 메모리 할당자가 담당한다.
- 비교적 느린 접근 속도
- 포인터를 통해 접근하고 메모리 단편화 발생 가능성 잇음
메모리 누수
순환 참조와 같은 경우에 메모리 누수가 생길 수 있다.
메모리 단편화
동적으로 할당하고 해제되면서 메모리 단편화가 발생할 수 있다. 충분한 메모리가 남아있어도 연속된 큰 블록을 할당할 수 없을 수 있다.
- 할당 크기와 메모리 할당자의 관리 방식에 따라 내부 단편화와 외부 단편화가 발생할 수 있다.
힙 메모리 고갈
프로세스가 사용할 수 있는 메모리보다 많은 힙 메모리를 요청하면 할당이 실패하거나 운영체제가 프로세스를 종료할 수 있다. 이는 할당된 버퍼의 경계를 넘어 쓰는 힙 버퍼 오버플로우와는 다른 문제다.
힙 버퍼 오버플로우
힙에 동적으로 할당된 버퍼의 경계를 초과해서 데이터를 쓰는 상황
- 안전한 Swift 코드에서는 배열 경계 검사 등이 메모리 손상을 막아주지만,
UnsafePointer계열 API나 C 코드와 상호 운용할 때는 힙 버퍼 오버플로우가 발생할 수 있다.