본문 바로가기

iOS/기초 다지기

[기초 다지기] 메모리 구조를 살펴보자

반응형

안녕하세요!

오늘은 ARC를 공부하기 위해, 기반 지식인 메모리 구조에 대해 알아보겠습니다!

어느 언어에서나 꼭 필요한 부분이기 때문에~ 열심히 정리해보겠습니다 :D


Memory ?? 🤔

프로그램이 실행되면 운영체제(OS)는 메모리(RAM)에 이 프로그램을 위한 공간을 할당해 줍니다.

공간은 총 4가지 (Code, Data, Heap, Stack)으로 나뉘어져 있습니다.

각 영역은 프로그램 실행 중 서로 다른 역할을 담당하며, 효율적인 메모리 관리를 위해 사용됩니다.

 

 

자, 이제 각 영역이 어떤 역할을 하는지 하나씩 알아볼까요?ㅎㅎ

 


 

1.  코드(Code) 영역

 

이 영역에는 실행할 프로그램의 기계어(2진수) 코드가 저장됩니다. 쉽게말하면, 프로그램의 명령어들이 저장되는 곳이죠!

또한, 프로그램 실행 중 코드가 변경되는 것을 방지하기 위해 읽기 전용(Read - Only) 형태로 저장됩니다. 

프로그램이 실행될 때 고정된 크기로 할당되며, 함수/ 조건문/ 반복문 등 실행코드가 여기에 포함됩니다.

 

장단점

장점

  • 프로그램 실행 중 코드가 변하지 않아 안정적
  • 코드가 효율적으로 저장되며, CPU 명령어 처리 속도가 빠름

단점

  • 실행 중 코드 수정이 불가능
  • 크기가 고정되므로 유연성이 부족

2. 데이터(Data) 영역

 

데이터 영역은 프로그램이 실행 중에 사용할 전역 변수정적 변수(static)가 저장되는 곳입니다.

초기화된 데이터 영역초기화되지 않은 데이터 영역(BSS)으로 구분하여 관리됩니다.

 


초기화된 데이터: 값이 미리 정의된 변수.

var name: String? = "sojin" // 초기화된 데이터

 

초기화되지 않은 데이터: 값이 초기화되지 않은 변수.

var age: Int?

 

예제코드를 한 번 살펴봅시다!

struct Constant {
    static let language = "Swift" // 정적 변수(상수) - 초기화된 데이터
}

var name: String? = "sojin" // 전역 변수 - 초기화된 데이터
var age: Int? // 전역 변수 - 초기화되지 않은 데이터

 

이처럼 초기값이 있는 변수(language, name)는 초기화된 데이터 영역에,
초기값이 없는 변수(age)는 초기화되지 않은 데이터 영역(BSS)에서 저장됩니다.

 

장단점

장점

  • 전역 변수와 정적 변수는 프로그램 종료 시까지 유지되어 관리가 쉬움

단점

  • 메모리를 많이 차지하며, 전역 변수 남용 시 프로그램이 비효율적이 될 수 있음

3. 힙(Heap) 영역

 

힙은 메모리 동적 할당을 위한 공간입니다.

Swift에서는 힙에 메모리를 할당하기 위해 C언어나 Objective-C처럼 malloc이나 calloc을 직접 호출하지 않습니다.

대신, 클래스 인스턴스(Class Instance), 클로저(Closure)와 같은 참조 타입의 값들이 자동으로 힙에 할당됩니다. 

 

이전 Object-C에서는 따로 retain, release 같은 명령어를 사용하여 메모리를 수동으로 관리해야했습니다.

하지만 Swift를 사용하면서 이런 수동 메모리 관리를 해본 적이 없죠?

 

이유는 Swift에서  ARC(Automatic Reference Counting) 가 메모리 관리를 자동으로 해주기때문입니다!

ARC가 힙에 저장된 객체의 참조 카운트를 관리하며, 해당 객체에 대한 참조가 더 이상 존재하지 않을 경우 메모리를 자동으로 해제합니다.

 

예제 코드를 한번 자세히 봅시다!!

class Person {
    var name: String
    init(name: String) { self.name = name }
}

let person1 = Person(name: "Sojin") // 힙에 저장
let person2 = person1               // 동일한 힙 메모리를 참조

 

Person 클래스는 참조 타입이기때문에 자동으로 힙에 할당되겠죠??

즉 Person(name: "Sojin")을 호출하면, Person 객체가 생성되고 힙 메모리에 저장이 됩니다.

변수 person1은 힙 메모리 주소(예: 0x100)를 스택에 저장하고, 이 주소를 통해 힙의 데이터를 참조합니다.

 

person2 = person1은 person2가 person1의 힙 메모리의 주소(0x100)를 복사합니다.

즉, person2는 스택에 새로운 공간이 생기고, 이곳에 0x100을 저장합니다

person1 person2는 동일한 힙 메모리를 참조(메모리 주소 참조)하고 있으므로 서로 하나의 데이터를 공유합니다.

 

메모리 쪽을 정리해보면 아래와 같습니다.

스택 (Stack):
----------------------
person1 : 0x100 (힙 주소)
person2 : 0x100 (힙 주소)
----------------------

힙 (Heap):
----------------------
0x100 : Person 객체 {
    name: "Sojin"
}
----------------------

 

여기서 ARC(Automatic Reference Counting)가 참조 카운트를 관리하며, 참조가 모두 사라지면 메모리를 자동으로 해제합니다.


ARC의 특징 (간단하게)

1. 참조 카운트 증가: 객체가 새로운 변수나 상수에 할당되면 참조 카운트가 증가합니다.

var person1 = Person(name: "Alice") // 참조 카운트: 1 
var person2 = person1 // 참조 카운트: 2

 

2. 참조 카운트 감소: 참조가 더 이상 필요하지 않거나 nil로 설정되면 카운트가 감소합니다.

person1 = nil // 참조 카운트: 1
person2 = nil // 참조 카운트: 0 -> 메모리 해제

 

3. 순환 참조(Circular Reference) 문제:
ARC는 순환 참조를 자동으로 해결하지 못합니다.
클로저나 두 객체가 서로를 강한 참조로 유지하면 참조 카운트가 0이 되지 않아 메모리 누수가 발생할 수 있습니다.
이를 해결하기 위해 weak 또는 unowned 키워드를 사용하여 약한 참조를 설정합니다.

class Person {
    var name: String
    weak var friend: Person? // 약한 참조
    init(name: String) { self.name = name }
}

 

더 자세히 ARC에대해 알고 싶다면  [Swift] ARC(Automatic Reference Counting) 를 참고해주세요!


장단점

장점

  • 크기 제한이 없고, 프로그램의 모든 함수에서 접근 가능
  • 동적으로 할당되어 유연성이 뛰어남

단점

  • 메모리를 직접 관리하지 않으면 메모리 누수(Memory Leak)가 발생할 수 있음

4. 스택(Stack) 영역

 

스택은 프로그램이 임시로 사용하는 메모리 공간입니다.

함수 호출 시 매개변수 지역 변수들이 이곳에 할당되며,

함수가 종료되면 해당 함수의 매개변수와 지역 변수가 자동으로 메모리에서 해제됩니다.

 

func printName(name: String) -> String {
	let message = "\(name)님 안녕하세요." // 지역 변수
	return message
}

let result = printName(name: "Sojin")

// name (함수의 매개변수) → Stack
// message (함수의 지역변수) → Stack

 

위 코드에서 함수의 매개변수인 name("Sojin")과 함수 내 지역 변수인 message("Sojin님 안녕하세요.")는 스택에 저장되겠죠?

물론 함수가 종료되면 스택에 저장된 name과 message는 자동으로 해제됩니다.

아래 예시를 보고 메모리 내부를 좀 더 쉽게 이해해 봅시다!

 

printName(name: "Sojin") 함수 실행 시

스택 (Stack):
---------------------
message: "Sojin님 안녕하세요."
name: "Sojin"
---------------------

 

printName(name: "Sojin") 함수 종료 시

스택 (Stack):
---------------------
(비어 있음)
---------------------

 

이때 스택은 LIFO(Last In First Out) 구조이기때문에 마지막에 들어온 값이 먼저 해제됩니다.

 

장단점

  • 장점:
    • CPU가 관리하므로 속도가 매우 빠름
    • 메모리를 명시적으로 해제할 필요가 없음
  • 단점:
    • 크기가 제한되어 있으며, 큰 데이터를 저장할 수 없음
    • 함수 호출 내에서만 접근 가능

언제 힙을 쓰고 스택을 쓸까?

 

에 할당:

  • 데이터의 크기를 미리 알 수 없거나 큰 데이터를 저장할 때 사용
  • 예: 클래스 인스턴스, 클로저, 동적 배열.

스택에 할당:

  • 함수 호출 내에서 일시적으로 사용되는 작은 데이터를 저장할 때 사용
  • 예: 매개변수, 지역 변수

 

클래스와 값 타입(구조체)의 선택 기준

특징 클래스 (참조 타입) 구조체 (값 타입)
저장 방식 힙 메모리에 데이터 저장, 스택에 주소 저장 스택에 데이터 직접 저장
복사 시 동작 참조를 복사 (데이터 공유) 데이터를 복사 (독립적 데이터 생성)
사용 상황 데이터 공유가 필요하거나, 동작(메서드)이 많을 때 간단한 데이터 모델링, 독립적 데이터가 필요할 때
성능 복잡하지만 데이터 공유에 유리 간단하고 빠름

 

 

⚠️ 주의 ⚠️ 
- 힙을 남용하면 메모리 누수가 발생할 수 있습니다.
- 스택을 과도하게 사용하면 스택 오버플로우로 인해 프로그램이 종료될 수 있습니다.
반응형

'iOS > 기초 다지기' 카테고리의 다른 글

[기초 다지기] 개념 정리 노트  (4) 2024.09.07
[기초 다지기] 동기(Sync) / 비동기(Async)  (0) 2023.01.02