메모리 구조
2024. 1. 23. 23:08ㆍCS
메모리 구조
코드 영역
- 실행가능한 컴파일된 기계어 코드
- 프로그램의 실행을 위한 실제 명령어들이 저장되는 곳입니다. 이 영역은 읽기 전용이며, 실행 파일의 텍스트 세그먼트에 해당합니다.
데이터 영역
- 전역 변수(Global Variables): 프로그램 전체에서 접근 가능한 변수들로, 데이터 영역에 저장됩니다.
- 정적 변수(Static Variables): 정적으로 선언된 변수들로, 해당 변수가 선언된 파일 내에서만 접근 가능하거나 클래스/구조체 내에서만 접근 가능한 경우가 있습니다.
- 정적 상수(Static Constants): 변경되지 않는 값들로, 프로그램의 실행 동안 고정된 값을 유지합니다.
- 문자열 리터럴(String Literals): 프로그램 코드 내에 직접 작성된 문자열 상수들로, 런타임 동안 변경될 수 없습니다.
- 전역 함수(Global Functions): 어디서나 호출할 수 있는 함수들로, 프로그램 시작 시 데이터 영역에 로드됩니다.
- 스태틱 메서드(Static Methods): 클래스나 구조체에 정의된 정적 메서드들로, 이들도 데이터 영역에 저장됩니다.
힙(Heap) 영역
- 용도: 동적으로 할당되는 데이터(객체)를 저장합니다.
- 메모리: 런타임에 크기가 결정되며, 개발자가 직접 할당 및 해제합니다.
- 특징
- 메모리는 필요에 따라 할당되며, 사용 후에는 명시적으로 해제해야 합니다
- 힙은 메모리가 비연속적으로 할당될 수 있으며, 크기에 제한이 없습니다.
- 메모리 누수: 할당된 메모리를 해제하지 않고 방치할 때 발생합니다.
- 스택에 비해 속도가 느리고 관리가 복잡합니다.
- 클래스 인스턴스: 클래스에서 생성된 객체들은 힙에 저장되며, 이들은 동적으로 할당되고 참조로 관리됩니다.
- 클래스 인스턴스를 생성할때, 이들은 힙에 저장되고 ARC에 의해 관리 됩니다.
- let myObject = MyClass() 와 같이 인스턴스 생성시 힙에 저장됩니다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var hemg: Person? = Person(name: "hemg")
// Person 인스턴스는 힙에 할당됩니다.
- 클로저: 힙에 저장됩니다. 클로저는 캡처 목록을 통해 주변 컨텍스트의 변수를 캡처할 수 있으며, 이 때문에 힙에 저장됩니다.
- { [weak self] in self.?doSomething() } 와 같은 클로저 에서
self
는 클로저에 의해 캡처됩니다.
- { [weak self] in self.?doSomething() } 와 같은 클로저 에서
var name = "hemg"
let greet = { [name] in
print("Hello, \(name)")
}
greet() greet 클로저는 name을 캡처하며 힙에 저장됩니다.
- 컬렉션 타입:
배열
,딕셔너리
,셋
이러한 컬렉션 타입들은 내부적으로 참조 타입의 요소를 보유할 수 있으며, 이 요소들은 힙에 저장됩니다.
- 힙은 동적으로 할당되는 데이터를 저장하는 영역으로, 개발자에 의해 수동으로 메모리 관리가 이루어집니다.
스택(Stack) 영역
- 용도: 함수 호출과 관련된 데이터(지역변수, 매개변수, 반환 주소등)를 저장합니다.
- 메모리: 컴파일 타임에 크기가 결정되며, 자동으로 할당 및 해제됩니다.
- 특징
- LIFO방식으로 작동합니다.
- 스택은 메모리가 연속적으로 할당되어야하며, 크기가 고정적입니다.
- 스택 오버플로우: 스택 크기를 초과하여 데이터를 넣으려 할 때 발생합니다.
- 속도가 빠르며 관리가 상대적으로 간단합니다.
- 지역 변수(Local Variables): 함수 내에서 선언된 변수들.
- 매개변수(Parameters): 함수로 전달된 매개변수들.
- 함수 호출 컨텍스트(Function Call Context): 함수 호출 시 필요한 정보(로컬 변수, 매개변수 등).
func greet(name: String) {
let greeting = Hello, \(name)
print(greeting)
}
greet(name: "hemg")
name 과 greeting 은 스택에 저장됩니다.
- 스택은 자동으로 메모리 관리가 이루어지며, 메모리 할당과 해제가 매우 빠릅니다. 하지만 고정된 크기로 인해 공간 제약이 있으며, 오버플로우가 발생할 수 있습니다.
스택과 힙의 주요 차이점
- 메모리 할당: 스택은 자동으로 메모리가 관리 되지만, 힙은 수동으로 메모리를 관리해야 합니다.
- 메모리 구조: 스택은 정적이고 연속적인 메모리 할당을 사용하며, 힙은 동적이고 비연속적인 메모리 할당을 사용합니다.
- 속도: 스택은 힙에 비해 메모리 접근 속도가 빠릅니다.
- 용량: 힙은 스택보다 훨씬 큰 메모리 할당을 지원합니다
값타입 참조 타입 차이점
값타입
(Value Types)- 구조체, 열거형, 기본데이터(Int, String, Bool등)
- 메모리: 값 타입의 인스턴스는 스택에 저장
- 동작 방식: 값 타입의 인스턴스는 데이터를 복사하여 전달합니다. 변수에 값을 할당하거나 함수에 인자로 전달할 때, 실제 값의 복사본이 생성되어 사용됩니다.
- 특징: 독립적입 인스턴스, 각 인스턴스는 독립적인 데이터 복사본을 가지고 있습니다.
- 높은 성능, 스택에 저장되므로 메모리 관리가 더 빠르고 효율적입니다.
참조 타입
(Reference Types)- 클래스(class)
- 메모리: 참조 타입의 인스턴스는 힙에 저장됩니다.
- 동작 방식: 참조 타입의 인스턴스는 메모리 주소를 통해 전달됩니다. 변수에 할당하거나 함수에 전달할때, 원본 데이터의 메모리 주소가 사용됩니다.
- 특징: 공유 인스턴스, 여러 변수가 동일한 인스턴스를 참조할 수 있습니다. ARC에 의한 메모리관리, 힙에 저장되므로 ARC가 메모리 관리를 담당합니다.
옵셔널(Optional)
- 기능: 값이 nil일 수 있는 변수를 안전하게 처리합니다.
- 메모리 사용: 옵셔널은 추가적인 정보(값이 존재하는지 여부)를 저장하기 위해 약간의 추가 메모리를 사용합니다.
- 옵셔널 체이닝과 메모리 관리:
- 옵셔널 체이닝을 사용할 때, 옵셔널 값이 nil인 경우 메모리에 접근하지 않아 안전합니다.
- nil 체크를 통해 불필요한 메모리 접근을 방지하고, 이로 인한 오류 가능성을 줄입니다.
- 옵셔널의 효과적 사용
- 예시: 옵셔널 바인딩, 옵셔널 체이닝, 옵셔널 강제 언래핑
- 메모리 관리:
- 옵셔널을 사용함으로써 '값이 없는 상태'를 안전하게 처리할 수 있으며, 이는 메모리 접근 오류를 방지하는 데 도움이 됩니다.
- 옵셔널을 과도하게 사용하면 메모리 사용량이 늘어날 수 있으므로 적절한 사용이 중요합니다.
스택은왜 자동으로 메모리 관리가 되는가?
- 스택은 매우 규칙적으로 데이터를 저장하고 제거하는 LIFO(Last In, First out)방식을 따릅니다. 이러한 특성 때문에 스택은 프로그램 실행중에 발생하는 함수 호출과 같은 임시 작업에 매우 효과적입니다.
- 특정
- 정해진 순서와 범위:
- 스택은 함수 호출과 내부의 지역 변수들을 순서대로 저장합니다. 함수가 호출되면 스택에는 그 함수의 지역 변수와 관련 정보가 순서대로 쌓입니다. 함수가 종료되면, 그 함수에 해당하는 모든 정보가 스택에서 제거됩니다.
- 스택 포인터:
- 스택 포인터라는 특별한 지시자를 사용하여 현재 스택의 상단을 가리킵니다. 함수가 호출될 때마다 스택 포인터는 상승하고, 함수가 종료될 때마다 스택 포인터는 내려가며 관련 메모리를 해제합니다.
- 컴파일 시 메모리 할당:
- 스택에 저장되는 데이터(지역 변수, 매개변수)의 크기는 컴파일 시간에 결정됩니다. 프로그램이 실행될 때 메모리 할당과 해제의 패턴이 결정되어 있습니다.
- 프로그램 실행중에 함수 호출이 순차적인 진행으로 이뤄집니다. 함수가 다른 함수를 호출하고, 반환되는 과정이 자연스럽게 스택의 특성과 일치합니다.
- 스택 프레임:
- 각 함수 호출은 스택 프레임이라는 블록을 스택에 생성합니다. 함수의 지역변수, 매개변수, 복귀주소 등 포함하고있습니다. 함수 호출시 스택 프레임이 스택에 푸쉬되고, 함수가 반환될때 팝되어 메모리에서 제거 됩니다.
- 스택 오버플로우:
- 스택 오버플로우는 함수 호출과 지역변수 할당으로 인해 스택의 크기가 그 용량을 초과할 때 발생합니다. 이는 스택 메모리가 제한된 크기를 가지고 있기 때문에 발생하는 것입니다.
- 정해진 순서와 범위:
- 전체적으로? 조금 크게 본다면
- 지역변수와 매개변수
- 스택 포인터
- 컴파일 시간에 결정되는 메모리 크기
- LIFO 메모리 관리
- 스택 프레임
힙은 왜 수동인거야?
- 힙 메모리가 수동 관리되는 이유
- 힙은 프로그램의 실행 중에 할당되고 해제되는 메모리 영역으로, 주로 객체나 큰 데이터 구조를 저장하는데 사용 됩니다.
- 힙 특징
- 런타임에 크기가 결정됩니다
- 힙에 저장되는 데이터의 크기는 프로그램 실행 중에 결정됩니다. 이는 컴파일 시간에 결정되는 스택과는 대조됩니다.
- 메모리 할당의 유연성
- 힙은 더 유연한 메모리 할당을 합니다. 프로그램 실행 중 사용자의 입력에 따라 필요한 메모리 크기가 결정되는 경우 해당 된다
- 수동 관리 필요성
- 힙 메모리는 자동으로 관리되지 않습니다. 개발자가 직접 할당 해제 합니다. 힙에 저장되는 객체의 경우 생명주기가 복잡하기 떄문에.. 하지만 Swift는 ARC가 자동으로 해주고 있습니다.
- 런타임에 크기가 결정됩니다
동적 메모리란? 무엇이냐?
- 동적 메모리는 프로그램의 실행 중에 할당되고 해제되는 메모리 입니다.
- 실행 시간에 결정되는 메모리 크기: 동적 메모리 할당은 프로그램이 실행되는 도중에 필요한 메모리 크기가 결정되고 할당됩니다.
- 클래스 인스턴스의 할당: Swift에서 클래스는 참조 타입입니다. 클래스 인스턴스를 생성할때, 이 인스턴스는 힙에 동적으로 할당됩니다.
- let object = MyClass()는 Myclass의 새 인스턴스를 힙에 할당합니다.
- ARC의 역할: Swift는 ARC를 사용하여 동적으로 할당된 객체의 생명주기는 관리 합니다. 개발자는 메모리 할당과 해제에 대해 신경 쓸 필요가 없으며, ARC가 필요 없어진 객체를 자동으로 해제합니다.
- 참조 카운팅: ARC는 객체에 대한 참조 횟수를 추적합니다. 객체에 대한 모든 강한참조가 제거되면, ARC는 해당 객체를 메모리에서 해제합니다.
- 메모리누수: ARC가 대부분 메모리 관리를 자동으로 처리하지만, 순환 참조와 같은 문제로 인해 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 [weak, unowned]를 사용하게 됩니다.
class MyClass {
var data: String
init(data: String) {
self.data = data
}
}
let myObject = MyClass(data: "안녕하세요.")
힙에 MyClass 인스턴스가 할당됩니다.
ARC가 myObject에 대한 참조가 더 이상 없을 때 메모리에서 해제합니다.