최근 CS 스터디를 진행하면서 나왔던 이야기 중 Swift는 Heap에 메모리가 할당 될 땐 동적할당, Stack에 메모리가 할당 될 땐 정적할당이 발생하는 것 아니냐!? 하는 이야기가 나왔었다. 나는 그렇게 알고 있었는데...
스터디에서 함께 참고하는 Java & JS 서적에는 이런 글이 있었다.
스택에는 지역변수, 매개변수, 함수가 저장되고 컴파일 시에 크기가 결정되며 '동적'인 특징을 갖는다.
힙은 동적 할당할 때 사용되며 런타임 시 크기가 결정된다. 힙은 '동적'인 특징을 갖는다.
책에는 스택에는 동적으로 메모리가 할당된다는 말은 없고 '동적'인 특징을 갖는다는 말 만 나와있었고, 힙은 동적 할당 + '동적'인 특징까지 갖는다고 나와있었다.
여기서 좀 혼란이 왔다. 내가 알고 있는 것이 제대로 된 것인지를 확인할 필요를 느꼈고 동적인 특징을 갖는 것이 메모리를 동적할당하는 것과 같은 맥란인건지... 아니면 Swift와 Java 등의 다른 언어에서 오는 차이점이 따로 있는건지... 정립이 되지 않아 조금 더 기초부터 알아보고자 한다.
정적 메모리 할당은 뭘까?
정적 메모리 할당은 변수에 대한 메모리가 컴파일 타임에 할당되고 프로그램 실행 내내 고정된 상태로 유지되는 메모리 할당 기술이다. 즉, 메모리의 크기와 위치는 프로그램이 컴파일될 때 결정되며, 메모리 할당부터 해제가 이루어질 때까지(lifetime of variable) 보존된다. 정적 메모리 할당은 크기가 고정되어 있고 전역 변수, 정적 변수 및 상수와 같이 컴파일 타임에 확인할 수 있는 변수에 사용된다.
간단한 예를 들어보자
let PI = 3.14159 // Constant PI is allocated in the static memory
var counter = 0 // Variable counter is allocated in the static memory
위의 예에서 상수 PI와 변수 counter는 고정된 값 + 컴파일 타임에 각각의 메모리가 할당되므로 정적 메모리 할당이 일어난다.
동적 메모리 할당은 뭘까?
동적 메모리 할당은 변수에 대한 메모리가 프로그램 실행 중에 런타임에 할당되는 메모리 할당 기술이다. 즉, 메모리의 크기와 위치는 프로그램의 필요에 따라 동적으로 결정된다. 동적 메모리 할당은 동적 크기를 가지며 배열, 문자열 및 객체와 같이 런타임에 메모리가 할당되는 변수에 사용된다. Swift에서 동적 메모리 할당은 ARC(Automatic Reference Counting) 메커니즘에 의해 관리된다. ARC는 개체에 대한 참조 수(Reference Count)를 추적하고 개체가 더 이상 사용되지 않을 때 메모리 할당을 해제한다.
이것도 예를 들어보자
var myArray = [1, 2, 3, 4] // Array myArray is allocated in the dynamic memory
myArray.append(5) // Adding an element to the array
위의 myArray는 하나의 고정된 값으로 이루어지지 않았고, 프로그램을 실행하여야(런타임 때) myArray에 새로운 값인 5의 메모리가 추가로 할당된다. 이렇게 컴파일 타임이 아닌 런타임 때 메모리를 상황에 맞게 늘리거나 줄일 수 있도록 설계된 것이 동적 메모리 할당이다.
여기까지가 기본적으로 알아야 할 정보...!
그래서 Swift 환경의 Stack에는 동적 메모리 할당이 일어날까!?
결론은 아니다.
스택은 로컬 변수 및 함수 호출을 저장하는 데 사용되는 메모리 영역이다. 함수가 호출될 때 자동으로 할당되고 함수가 반환될 때 할당이 해제되는 로컬 메모리 공간이다. Swift에서 스택의 로컬 변수는 정적 메모리 할당을 사용하여 할당되며, 여기서 메모리 크기는 고정되어 컴파일 타임에 결정된다. 함수가 호출되면 컴파일러는 함수 내에서 선언된 지역 변수를 위해 스택에 고정된 양의 메모리를 별도로 설정한다. 이 메모리는 함수가 반환될 때 할당 해제된다.
Stack과 메모리 할당 코드도 한 번 보자!
func calculateSum(a: Int, b: Int) -> Int {
let sum = a + b // Allocates a new Int variable on the Stack
return sum
}
let result = calculateSum(a: 5, b: 7) // Calls the calculateSum() function and assigns the result to a new variable on the Stack
이 경우 sum 상수는 result 상수에 메모리가 할당될 때 calculateSum 함수를 실행시키고 복사된 값이 Stack에 올라간다. 그리고 복사된 값 a와 b의 값이 연산처리되어 sum을 return하고 바로 Stack에서 사라진다. 즉 값 타입으로 이루어진 연산의 경우 컴파일타임에 메모리를 할당받고, 함수 종료시 자동으로 Stack에 할당되었던 메모리들이 전부 해제되기 때문에 동적 메모리 할당이 일어날 필요가 없는 것이다.
오케이... 그러면 Heap에서 동적 메모리 할당이 일어나는건 맞는거지?
결론은 맞다.
힙은 런타임에 동적으로 할당되는 메모리 영역이다. 전체 프로그램이 공유하는 전역 메모리 공간이다. Swift에서 개체는 참조 타입을 사용하여 힙에 할당된다. 즉, 개체가 저장된 메모리 위치에 대한 참조로 저장된다. 힙에 있는 개체의 메모리 크기는 고정되어 있지 않으며 런타임 중에 필요에 따라 동적으로 조정할 수 있다.
Heap과 메모리 할당 코드도 한 번 보자!
class MyClass {
var value: Int
init(value: Int) {
self.value = value
}
}
var myObject = MyClass(value: 10) // Allocates a new MyClass object on the Heap
위 코드의 경우 Class가 참조 타입으로 Heap에 저장되고 참조 타입의 특성상 런타임에 동적 메모리 할당이 일어나므로 value 값에 따라 언제든지 메모리의 크기가 바뀌어야한다.
그럼 결론적으로 위의 책에서 언급한 '동적'인 특징은 뭘까?
책에서 언급된 부분은 정적 메모리 할당, 동적 메모리 할당과는 조금 거리가 먼 이야기라고 보여진다.
어쩌면 Stack에서 메모리를 할당할 때 LIFO의 형태로 메모리를 할당/해제하는 프로세스의 동적인 형태와 Heap에서 메모리를 할당/해제하는 프로세스인 사용되지 않은 메모리를 찾아서 끊임없이 움직이는 동적인 형태를 일컬어 나타낸 표현이 아닐까라는 생각..!
또한 "정적 메모리 할당"이라는 다소 추상적인 표현에도 불구하고 결국 Stack에 있는 지역변수, 함수, 매개변수들의 메모리가 컴파일타임에 할당되었다가 Stack 내의 코드가 종료되면 사라지는 형태를 두고도 동적이라고 볼 수 있겠다!
'TIL > CS(feat. Swift)' 카테고리의 다른 글
컴파일타임(CompileTime)과 런타임(RunTime) (0) | 2023.02.23 |
---|---|
COW(Copy on Write) 훑어보기 (0) | 2023.02.16 |
디자인 패턴 훑어보기_MVC 패턴 (0) | 2023.02.06 |
디자인 패턴 훑어보기_옵저버 패턴 (0) | 2023.02.06 |
디자인 패턴 훑어보기_싱글톤 패턴 (0) | 2023.02.05 |