[CS] 메모리 구조(Memory Structure) 이해하기

2022. 3. 24. 04:28Programming/iOS_Swift

  메모리 구조에 대해 설명하기에 앞서, '메모리'가 무엇인지 가볍게 짚고 넘어가야 할 것 같다. 사실 나도 정리하기 전까지만 해도 메모리는 램(RAM)이랑 똑같은 말이고 그냥 램이 높으면 높을 수록 컴퓨터가 빨라지고 좋은거다 이정도만 알고 있었다.

메모리(Memory)란?

  메모리란 RAM(Random Access Memory)의 또다른 표현으로, 단어 표현 그대로 뭔가 기억과 관련된 행동을 한다. 즉 CPU가 처리할 데이터나 명령어들을 임시로 기억하고 저장될 수 있게하는 작업 공간 역할을 하는 것이다. 정리하면, 사용자가 어떤 프로그램을 사용할때, 해당 정보에 빠르게 접근할 수 있게끔 컴퓨터가 관련 정보들을 임시로 기억해서 저장해두는 공간을 의미한다. 한 마디로 메모리가 크면 클수록 사용자는 여러 프로그램을 사용할 수 있고 이는 여러 작업을 한 번에 수행할 수 있다는 것이다. 게임하면서 동영상도 틀고 문서작업도 하고 다 할 수 있다! 단, 메모리가 수용할 수 없을정도로 많은 프로그램을 실행한다면 컴퓨터에서 메모리가 부족하다고 알려줄 것이다

이런 이미지 한번 쯤 본적 있을 것이다.

  이제 메모리가 뭔지는 대충 알겠다. 그러면 이제 메모리 구조에 대해 살펴보자.

 

메모리 구조

  방금 전에, 메모리는 임시로 기억하고 저장해두는 공간이라고 했다. 자 그러면 만약에 사용자가 어떤 프로그램을 실행해달라고 컴퓨터에 시켰을 때, 우리의 운영체제(OS)는 이제 명령을 한다. 그 프로그램의 크기 만큼 메모리에 공간을 할당하라고 한다. 그러면 메모리는 명령에 의해 위 사진처럼 4개의 영역에 용도에 맞게끔 할당해준다. 예를 들어, A라는 프로그램을 사용자가 실행한다면, 해당 프로그램의 코드가 있을테고 전역 변수, 지역 변수, 매개 변수 등등이 있을 것이다. 그러면 메모리는 저런 코드나 변수 친구들을 메모리 내의 각 영역에 맞게끔 할당해주는 것이다.

  이제 메모리의 내부의 각 영역들을 살펴보면, 다음과 같이 4개의 영역으로 이루어져 있다. Code, Data, Heap, Stack 영역으로 구분되어진다. 다음으로 각 영역에 대해서 살펴보자.

 

Code 영역

  코드 영역은 텍스트(Text)영역으로 불리기도 하며, 표현 그대로 우리가 실행하고자 하는 프로그램의 소스코드가 저장되는 영역이다.

 

1. 중간에 사라지지 않고 프로그램이 시작할때 생성되고 프로그램이 끝날 때 메모리에서 지워진다.

2. 프로그램의 소스코드들은 컴파일된 기계어(이진수)로 저장된다.

3. 일반적으로 함수가 Code영역에 해당된다.  

Data 영역

  데이터 영역은 프로그램의 전역 변수(Global 변수)와 정적 변수가 저장되는 영역이다. Swift로 생각했을 때, 타입 프로퍼티 Static 변수를 떠올리면 될 것 같다. 여기서 말하는 전역 변수란 함수나 메소드, 클로저 혹은 타입 컨텍스트 밖에 정의된 변수를 의미한다.

 

1. Code영역과 마찬가지로 중간에 메모리에서 사라지지 않고 프로그램이 끝날 때까지 메모리에 남아있는다.

 

Heap 영역

  동적으로 할당된 변수가 저장되는 영역으로, Swift에서는 클래스의 인스턴스나 클로저 같이 참조 타입에 해당하는 것들이 힙 영역에 저장된다.

 

1. 사용자가 직접 힙 영역을 통제해서 메모리를 관리할 수 있다는 점이 특징이다.

  Swift에서는 ARC라는 훌륭한 기능 때문에 사용자가 직접관리를 안해줘도 되지만, 종종 강한 순환 참조 현상이 발생해서 메모리 누수가 발생하는 경우가 있다. 이럴 때, 사용자는 해당 부분을 직접 통제해줌으로써 힙 영역을 관리할 수 있다. 만약 메모리 누수가 발생한 상태로 계속 내버려 둔다면, 프로그램이 느려지는 소요가 생기기도 하고 그 만큼 딴 거 실행할 수 있는 공간을 그냥 날려버리는 것이다. 강한 순환 참조 현상 관련해서는 이 글 참조.

 

2. 힙 영역의 크기는 런타임 시에 결정된다.

3. 메모리의 낮은 주소에서 높은 주소로 할당된다.

  즉 예를 들어, 어떤 클래스의 인스턴스 4개가 있을 때 초기화된 순서대로 메모리 주소가 낮은 주소에서 높은 주소로 할당된다.

class Soldier {

}

var firstSoldier = Soldier()
var secondSoldier = Soldier()
var thirdSoldier = Soldier()
var fourthSoldier = Soldier()

address(o2: &firstSoldier) // 0x10297c240
address(o2: &secondSoldier) // 0x10297c248
address(o2: &thirdSoldier) // 0x10297c250
address(o2: &fourthSoldier) // 0x10297c258

  위의 코드를 보면 메모리 주소가 순서대로 올라가고 있는 걸 확인할 수 있다.

Stack 영역

  스택 영역은 지역 변수와 매개변수가 저장되는 영역이다.

 

1. 스택 영역은 함수의 호출과 함께 메모리에 할당되며, 함수의 호출이 끝나면 메모리에서 소멸된다.

2. 스택 영역의 크기는 컴파일 시에 결정된다.

3. 스택 영역이 커질 수록 힙 영역이 작아지고 힙 영역이 커질 수록 스택 영역이 작아진다.

4. 힙 영역과는 반대로 메모리의 높은 주소에서 낮은 주소로 할당된다.

  바로 예시로 확인해보자. 지역 변수를 4개 만들어줬고 메모리 주소를 확인했다.

    func stackMemoryTest() {
        var soldier1 = "Tonic"
        var soldier2 = "Ethan"
        var soldier3 = "Dal"
        var soldier4 = "Park"

        address(o1: &soldier1) // 0x16ce898c0
        address(o1: &soldier2) // 0x16ce898b8
        address(o1: &soldier3) // 0x16ce898b0
        address(o1: &soldier4) // 0x16ce898a8
    }

  위의 코드를 보면 메모리 주소가 순서대로 높은 주소에서 낮아졌음을 확인할 수 있다.

 

정리하며

  프로그램을 설계함에 있어 메모리 관리는 필수 중에 필수이다. 내 경험을 비추어 생각해볼 때, 처음 앱을 출시했을 때 메모리 관리를 전혀 하지 않아서 상당 부분에서 메모리 누수가 발생했던 기억이 있다. 사실 그 때 당시에는 메모리에 대해 잘 모르기도 했고 '굳이 관리해야 하나?', '이게 유저에게 얼마나 큰 영향을 미치겠어' 라는 생각이 가득했던 것 같다. 메모리 관리를 위해 코드를 수정하면서도 그냥 막연하게 관련 개념도 없이 그냥 해야하니까 정도로만 생각했다. 물론 지금은 메모리의 중요성에 대해 깨달았고 매우 반성하고 있다!

  메모리의 중요성을 알았다면, 메모리가 어떻게 이뤄졌는지 찾아보는 것은 자연스러운 흐름인 것 같다. 그런 점에서 이번 메모리 구조 정리는 생각보다 재밌었다.