메모리 구조

2024. 1. 23. 23:08CS

메모리 구조

image

코드 영역

  • 실행가능한 컴파일된 기계어 코드
    • 프로그램의 실행을 위한 실제 명령어들이 저장되는 곳입니다. 이 영역은 읽기 전용이며, 실행 파일의 텍스트 세그먼트에 해당합니다.

데이터 영역

  1. 전역 변수(Global Variables): 프로그램 전체에서 접근 가능한 변수들로, 데이터 영역에 저장됩니다.
  2. 정적 변수(Static Variables): 정적으로 선언된 변수들로, 해당 변수가 선언된 파일 내에서만 접근 가능하거나 클래스/구조체 내에서만 접근 가능한 경우가 있습니다.
  3. 정적 상수(Static Constants): 변경되지 않는 값들로, 프로그램의 실행 동안 고정된 값을 유지합니다.
  4. 문자열 리터럴(String Literals): 프로그램 코드 내에 직접 작성된 문자열 상수들로, 런타임 동안 변경될 수 없습니다.
  5. 전역 함수(Global Functions): 어디서나 호출할 수 있는 함수들로, 프로그램 시작 시 데이터 영역에 로드됩니다.
  6. 스태틱 메서드(Static Methods): 클래스나 구조체에 정의된 정적 메서드들로, 이들도 데이터 영역에 저장됩니다.

힙(Heap) 영역

  • 용도: 동적으로 할당되는 데이터(객체)를 저장합니다.
  • 메모리: 런타임에 크기가 결정되며, 개발자가 직접 할당 및 해제합니다.
  • 특징
    • 메모리는 필요에 따라 할당되며, 사용 후에는 명시적으로 해제해야 합니다
    • 힙은 메모리가 비연속적으로 할당될 수 있으며, 크기에 제한이 없습니다.
    • 메모리 누수: 할당된 메모리를 해제하지 않고 방치할 때 발생합니다.
    • 스택에 비해 속도가 느리고 관리가 복잡합니다.
  1. 클래스 인스턴스: 클래스에서 생성된 객체들은 힙에 저장되며, 이들은 동적으로 할당되고 참조로 관리됩니다.
    1. 클래스 인스턴스를 생성할때, 이들은 힙에 저장되고 ARC에 의해 관리 됩니다.
    2. let myObject = MyClass() 와 같이 인스턴스 생성시 힙에 저장됩니다.
class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}
var hemg: Person? = Person(name: "hemg")
// Person 인스턴스는 힙에 할당됩니다.
  1. 클로저: 힙에 저장됩니다. 클로저는 캡처 목록을 통해 주변 컨텍스트의 변수를 캡처할 수 있으며, 이 때문에 힙에 저장됩니다.
    1. { [weak self] in self.?doSomething() } 와 같은 클로저 에서 self는 클로저에 의해 캡처됩니다.
var name = "hemg"
let greet = { [name] in
    print("Hello, \(name)")
}
greet() greet 클로저는 name을 캡처하며 힙에 저장됩니다.
  1. 컬렉션 타입: 배열, 딕셔너리, 이러한 컬렉션 타입들은 내부적으로 참조 타입의 요소를 보유할 수 있으며, 이 요소들은 힙에 저장됩니다.
  • 힙은 동적으로 할당되는 데이터를 저장하는 영역으로, 개발자에 의해 수동으로 메모리 관리가 이루어집니다.

스택(Stack) 영역

  • 용도: 함수 호출과 관련된 데이터(지역변수, 매개변수, 반환 주소등)를 저장합니다.
  • 메모리: 컴파일 타임에 크기가 결정되며, 자동으로 할당 및 해제됩니다.
  • 특징
    • LIFO방식으로 작동합니다.
    • 스택은 메모리가 연속적으로 할당되어야하며, 크기가 고정적입니다.
    • 스택 오버플로우: 스택 크기를 초과하여 데이터를 넣으려 할 때 발생합니다.
    • 속도가 빠르며 관리가 상대적으로 간단합니다.
  1. 지역 변수(Local Variables): 함수 내에서 선언된 변수들.
  2. 매개변수(Parameters): 함수로 전달된 매개변수들.
  3. 함수 호출 컨텍스트(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)방식을 따릅니다. 이러한 특성 때문에 스택은 프로그램 실행중에 발생하는 함수 호출과 같은 임시 작업에 매우 효과적입니다.
  • 특정
    • 정해진 순서와 범위:
      • 스택은 함수 호출과 내부의 지역 변수들을 순서대로 저장합니다. 함수가 호출되면 스택에는 그 함수의 지역 변수와 관련 정보가 순서대로 쌓입니다. 함수가 종료되면, 그 함수에 해당하는 모든 정보가 스택에서 제거됩니다.
    • 스택 포인터:
      • 스택 포인터라는 특별한 지시자를 사용하여 현재 스택의 상단을 가리킵니다. 함수가 호출될 때마다 스택 포인터는 상승하고, 함수가 종료될 때마다 스택 포인터는 내려가며 관련 메모리를 해제합니다.
    • 컴파일 시 메모리 할당:
      • 스택에 저장되는 데이터(지역 변수, 매개변수)의 크기는 컴파일 시간에 결정됩니다. 프로그램이 실행될 때 메모리 할당과 해제의 패턴이 결정되어 있습니다.
    • 프로그램 실행중에 함수 호출이 순차적인 진행으로 이뤄집니다. 함수가 다른 함수를 호출하고, 반환되는 과정이 자연스럽게 스택의 특성과 일치합니다.
    • 스택 프레임:
      • 각 함수 호출은 스택 프레임이라는 블록을 스택에 생성합니다. 함수의 지역변수, 매개변수, 복귀주소 등 포함하고있습니다. 함수 호출시 스택 프레임이 스택에 푸쉬되고, 함수가 반환될때 팝되어 메모리에서 제거 됩니다.
    • 스택 오버플로우:
      • 스택 오버플로우는 함수 호출과 지역변수 할당으로 인해 스택의 크기가 그 용량을 초과할 때 발생합니다. 이는 스택 메모리가 제한된 크기를 가지고 있기 때문에 발생하는 것입니다.
  • 전체적으로? 조금 크게 본다면
  1. 지역변수와 매개변수
  2. 스택 포인터
  3. 컴파일 시간에 결정되는 메모리 크기
  4. LIFO 메모리 관리
  5. 스택 프레임

힙은 왜 수동인거야?

  • 힙 메모리가 수동 관리되는 이유
    • 힙은 프로그램의 실행 중에 할당되고 해제되는 메모리 영역으로, 주로 객체나 큰 데이터 구조를 저장하는데 사용 됩니다.
  • 힙 특징
    • 런타임에 크기가 결정됩니다
      • 힙에 저장되는 데이터의 크기는 프로그램 실행 중에 결정됩니다. 이는 컴파일 시간에 결정되는 스택과는 대조됩니다.
    • 메모리 할당의 유연성
      • 힙은 더 유연한 메모리 할당을 합니다. 프로그램 실행 중 사용자의 입력에 따라 필요한 메모리 크기가 결정되는 경우 해당 된다
    • 수동 관리 필요성
      • 힙 메모리는 자동으로 관리되지 않습니다. 개발자가 직접 할당 해제 합니다. 힙에 저장되는 객체의 경우 생명주기가 복잡하기 떄문에.. 하지만 Swift는 ARC가 자동으로 해주고 있습니다.

동적 메모리란? 무엇이냐?

  • 동적 메모리는 프로그램의 실행 중에 할당되고 해제되는 메모리 입니다.
    • 실행 시간에 결정되는 메모리 크기: 동적 메모리 할당은 프로그램이 실행되는 도중에 필요한 메모리 크기가 결정되고 할당됩니다.
  1. 클래스 인스턴스의 할당: Swift에서 클래스는 참조 타입입니다. 클래스 인스턴스를 생성할때, 이 인스턴스는 힙에 동적으로 할당됩니다.
    • let object = MyClass()는 Myclass의 새 인스턴스를 힙에 할당합니다.
  2. ARC의 역할: Swift는 ARC를 사용하여 동적으로 할당된 객체의 생명주기는 관리 합니다. 개발자는 메모리 할당과 해제에 대해 신경 쓸 필요가 없으며, ARC가 필요 없어진 객체를 자동으로 해제합니다.
  3. 참조 카운팅: ARC는 객체에 대한 참조 횟수를 추적합니다. 객체에 대한 모든 강한참조가 제거되면, ARC는 해당 객체를 메모리에서 해제합니다.
  4. 메모리누수: ARC가 대부분 메모리 관리를 자동으로 처리하지만, 순환 참조와 같은 문제로 인해 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 [weak, unowned]를 사용하게 됩니다.
class MyClass {
    var data: String
    init(data: String) {
        self.data = data
    }
}
let myObject = MyClass(data: "안녕하세요.")
힙에 MyClass 인스턴스가 할당됩니다.
ARC가 myObject에 대한 참조가 더 이상 없을 때 메모리에서 해제합니다.

 

 

https://hackmd.io/LEwMHElgQiy1t7JyoYihkg