안녕하세요!
오늘은 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 |