TeamCoook / iOSInterviewQuestions

✅ iOS 개발자 기술 면접 대비
18 stars 0 forks source link

[레벨 0] `2주차` 3. iOS에서의 메모리 구조와 관리 방식에 대해 자세히 설명해주세요. #3

Open longlivedrgn opened 3 months ago

longlivedrgn commented 3 months ago
soo941226 commented 3 months ago

iOS 앱의 메모리 구조(힙, 스택, 코드 영역 등)와 각 영역의 특징에 대해 설명해주세요.

저는 iOS 앱의 메모리 구조가 일반적인 운영체제와 크게 다르지 않은 것으로 알고 있습니다. 따라서 크게 코드, 데이터, 스택, 힙이라고 하는 영역으로 분리가 되는데요. 코드는 소스코드가 올라가고, 데이터는 전역변수가 올라가고, 스택은 지역변수 및 함수, 값타입 등이 올라갑니다. 특히 여기까지는 컴파일 시점에 사이즈를 어느 정도 가늠을 할 수 있어서, 실제로 계산된 크기를 가지고 실행이 됩니다. 힙은 런타임에 사이즈가 동적으로 늘어날 수 있는 영역이며, 그 특성상 특히 콜렉션 타입의 요소들이 할당되는 영역입니다. 외에도 클로저를 포함한 참조타입의 인스턴스나 실존 타입들이 할당되게 됩니다.

힙 영역에서 객체가 어떻게 할당되고 관리되는지 설명해주세요.

두 가지 관점에서 이야기를 풀어나가야할 것 같습니다. 앞서 참조타입, 실존타입 등이 힙 영역에 할당이 될 수 있다고 말씀드렸는데요. 이를 조금 어렵게 이야기를 해보면 다형성을 갖게 되는 인스턴스들은 모두 힙영역에 할당이 된다고 말씀드릴 수 있을 것 같습니다. 이는 하나의 인스턴스가 서로 다른 타입으로 해석이 될 수 있다는 말인데, 컴퓨터가 런타임에 실제로 어떤 인스턴스를 다른 타입으로 해석하기 위해서는, 컴퓨터는 멍청하기 때문에 관련된 데이터가 반드시 존재해야 합니다. 실제 메커니즘은 상이하나 둘다 테이블을 통해 이를 관리하게 되는데요. Swift에서 참조타입의 인스턴스들은 v-table을 통해서 해당 인스턴스의 실제타입을 확인할 수 있고, 실존타입의 인스턴스들은 실존타입 내 protocol witness table을 통해 인스턴스의 실제타입을 확인할 수 있습니다.

다른 하나의 관점은 생명주기와 관련된 것입니다. 비교를 위해 먼저 스택을 짚고 넘어가면, 스택에 올라가는 값타입의 인스턴스는 어떤 맥락에서 사용이 끝나면, 바로 버릴 수 있습니다. 다른 맥락에서 이를 사용하려고 하면 인스턴스가 복사가 되며, 새로운 인스턴스를 생성하기 때문입니다. 하지만 힙영역에 올라가는 참조타입의 인스턴스들은 그럴 수 없는데요. 어떤 맥락에서 사용이 끝나더라도 다른 곳에서 이를 사용하면, 똑같은 인스턴스를 가리키고 사용을 하기 때문에, 메모리에서 해제가 되면 크리티컬한 에러가 될 수 있기 때문입니다. 스위프트에서는 이를 ARC라고 하는 기법으로 풀어내었는데 컴파일 타임에 참조타입에 레퍼런스 카운트라는 프로퍼티를 삽입하고, 사용 전후에 해당 카운트를 올리고 내리면서 0이 될 때 해제가 될 수 있도록 만들어 놓은 것입니다. 실존타입의 인스턴스의 경우에는 또 value witness table이라고 하는 테이블을 내부에 두어 이를 통해 메모리에서 해제가 될 수 있도록 만들어져 있습니다.

스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정을 설명해주세요.

간단하게 후입선출 형식으로 맥락이 끝나면 바로바로 메모리에서 해제가 되는 것으로 알고 있는데요. 이 때 재귀함수처럼 맥락이 굉장히 길어질 수 있는 경우는 stack overflow 에러를 조심해야할 필요가 있습니다. 실제로 이를 회피하기 위해서, 상용앱에서 재귀함수를 작성할 때에는 꼬리재귀의 형태로 작성하는 게 좋습니다

ohdair commented 3 months ago

코드 영역

스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정

함수 호출 시 지역 변수, 매개변수, 리턴 값이 저장되며, 함수가 종료되면 메모리가 해제된다. 함수 내 다른 함수를 호출했다면, 마지막 함수가 값을 반환 할 때까지 함수는 다른 함수가 종료될 때까지 메모리 해제를 기다리게 된다. ​ 하지만 스택 영역에서는 한정된 크기로 재귀 형식으로 메모리가 쌓이게 되면 스택 오버 플로우가 발생한다. 그리고 함수가 종료되면 변수의 값들은 메모리가 해제된 상태로 접근할 수 없게 된다.

llimental commented 3 months ago
1. iOS 앱의 메모리 구조(힙, 스택, 코드 영역 등)와 각 영역의 특징에 대해 설명해주세요. - 프로세스가 생성되면 커널 영역에 PCB가 생성되면서 사용자 영역에는 크게 코드, 데이터, 힙, 스택 영역으로 나뉘어 저장이 됩니다. ### 1-1) 코드 영역 - 텍스트 영역이라고도 부르며 실행할 수 있는 코드, 기계어로 이루어진 명령어가 저장되는 공간입니다. 명령어가 저장되기 때문에 쓰기 작업이 금지되어 있습니다. ### 1-2) 데이터 영역 - 프로그램이 실행되는 동안 유지할 데이터가 저장되는 공간입니다. 가령 전역 변수가 있으며 이전의 코드 영역과 데이터 영역은 프로그램을 구성하는 명령어, 실행되는 동안 유지할 데이터로 이루어져 있기 때문에 크기가 변하지 않습니다. 그래서 정적 할당 영역이라고도 합니다. ### 1-3) 힙 영역 - 개발자가 직접 할당할 수 있는 저장공간입니다. 프로그래밍 과정에서 이 영역에 메모리를 할당했다면 반환하는 작업도 필요합니다. 반환하지 않는다면 할당한 공간이 메모리에 계속 남게 되어 메모리 누수를 야기할 수 있습니다. - 다른 세 영역과 달리 런타임 시에 결정되기 때문에 데이터의 크기가 확실하지 않을 때 사용합니다. ### 1-4) 스택 영역 - 데이터 영역과 다르게 데이터를 일시적으로 저장하는 공간입니다. 가령 메서드의 실행이 끝나면 사라지는 매개 변수, 지역 변수가 이에 해당합니다. 또한 힙 영역과 스택 영역은 실시간으로 그 크기가 변할 수 있기 때문에 동적 할당 영역이라고 부릅니다. 가변적이기 때문에 서로 주소가 겹치지 않게 일반적으로 힙 영역은 메모리의 낮은 주소에서 높은 주소로 할당되고, 스택 영역은 높은 주소에서 낮은 주소로 할당됩니다. - 코드, 데이터 영역과 마찬가지로 컴파일 타임에 결정됩니다.
2. 힙 영역에서 객체가 어떻게 할당되고 관리되는지 설명해주세요. - 클래스, 클로저와 같은 참조 타입이 힙 영역에 할당됩니다. Swift는 ARC를 통해 객체에 대한 참조가 늘어나면 카운팅을 1 늘리고, 줄어들면 카운팅을 1 줄이는 식으로 관리하고, 카운팅이 0이 되면 메모리에서 해제하는 방식을 사용하기 때문에 직접 메모리를 해제하기보다는 객체에 대한 참조 관리를 한다고 생각합니다.
3. 스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정을 설명해주세요. - 함수 호출이 되면 함수의 매개변수와 로컬 변수는 스택에 할당되는데, LIFO(후입선출) 구조로 관리되기 때문에 먼저 할당된 변수가 마지막에 해제됩니다. 데이터의 크기가 불확실하거나 큰 데이터의 경우에는 힙에 저장하고, 이외에는 스택에 주로 쓰지만 과도한 메모리를 할당하게 되면 스택 오버플로우가 발생하게 되고 iOS에서는 이 경우에 앱이 크래시로 이어질 수 있습니다.
SunnnySong commented 3 months ago

1️⃣ iOS 앱의 메모리 구조(힙, 스택, 코드 영역 등)와 각 영역의 특징에 대해 설명해주세요.

코드 영역

데이터 영역

힙 영역

스택 영역

힙과 스택의 관계

2️⃣ 힙 영역에서 객체가 어떻게 할당되고 관리되는지 설명해주세요.

힙 영역에서 객체 할당 순서

  1. 클래스 인스턴스 생성
  2. 힙 영역에서 적절한 크기의 사용되지 않는 메모리 블럭을 찾기 위해 힙 영역 탐색
    1. 메모리 할당 전에 탐색이 필요하므로 스택 영역의 메모리 할당보다 시간이 더 걸림
  3. 적절한 메모리 블럭을 찾았다면, 기존 데이터를 초기화하고 새로운 클래스 인스턴스로 덮음
  4. 클래스 인스턴스가 저장된 힙 메모리 주소를 스택에 저장

힙 영역 관리

3️⃣ 스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정을 설명해주세요.

스택 영역에서 함수호출/로컬변수 메모리 할당 과정

  1. 함수 호출시 스택 프레임 생성

    • ❓스택 프레임

      : 함수 실행에 필요한 정보를 저장하는 메모리 블록

      • 로컬 변수, 함수 호출의 반환 주소, 함수의 파라미터, 리턴값 등
  2. 스택 프레임의 생성으로 스택 포인터 감소 (=0과 더 가까워짐)
  3. 함수 실행으로 값 할당
    1. 각 함수는 자신만의 스택 프레임을 갖기 때문에, 함수 간 로컬 변수의 값은 격리되어 있다.
  4. 함수 실행 종료시 스택 프레임 해제 및 스택 포인터 증가
longlivedrgn commented 3 months ago

앱의 메모리 구조(힙, 스택, 코드 영역 등)와 각 영역의 특징에 대해 설명해주세요.

코드 영역

데이터 영역

힙 영역

스택 영역

힙 영역에서 객체가 어떻게 할당되고 관리되는지 설명해주세요.

[자세한 ARC는 다음 레벨에서 정리할 예정]

ARC

스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정을 설명해주세요.

  1. 함수 호출
    • 호출된 함수의 스택 프레임이 스택에 추가가 된다.
    • 스택 프레임에는 함수의 매개변수, 복귀 주소, 이전 스택 프레임의 포인터가 저장된다.
    • 함수 안의 로컬 변수들도 해당 스택 프레임에 저장된다.
  2. 함수 호출 완료
    • 함수가 종료되면 해당 함수의 스택 프레임이 스택에서 제거가된다.
    • 스택 포인터가 감소하면서 이전 스택 프레임으로 돌아간다.
ueunli commented 3 months ago

iOS 앱의 힙/스탭/코드/데이터 영역은 일반적인 힙/스택/코드/데이터 영역과 크게 다르지 않다. 관련된 걸 굳이 꼽자면, iOS를 처음 공부하던 무렵 '기기의'가 아닌 '앱의' 힙/스택/코드/데이터 영역을 지칭한다는 걸 알고 의외라고 생각했던 기억이 있다. 물리적인 파일시스템 등을 차치하고 보면 앱 '활동'의 모든 명령과 처리는 네 가지 영역 위를 오가며 이루어지는 과정인 셈이다. 둘째, 셋째 질문은 곧 '앱의 컴파일과 런타임에서의 명령 처리 과정'을 나눠 설명하라는 말과 같다.

네 영역의 크기는 언제 결정되며 각자의 정보는 어떤 기준으로 나눠 가질까? 공부하다 보면 코드·데이터에 비해 힙·스택 영역을 중점적으로 공부하게 된다. 전자는 컴파일 타임에, 후자는 런타임에 구성되는 편이라 그런 듯하다.

코드 영역은 개발자가 작성한 '코드'를 흔히 기계어라고 부르는 형태의 '코드'로 컴파일러에 의해 컴파일타임에 write되며 런타임에 read되기만 하는 곳이다. 어쨌든 크기도 내용도 빌드와 함께 끝나는 읽기 전용 공간인데 직접 읽을 일도 없으므로 사실상 개발자가 어떻게 할 구석이 없다. 데이터 영역은 그래도 타입에 대한 정보나 전역변수가 저장되기에 조금 관심을 가질 만하다.

힙과 스택은 자료구조로서의 포괄적인 의미가 있어 개념적으로는 쉽지만 실전에서 고려할 일이 많은 영역이다. 스택 영역은 자료구조로서의 스택과 마찬가지로 '가벼운' 이미지다. 자료구조 스택처럼, 코드 영역의 명령이 순차적으로 실행되면서 필요한 정보들, 즉 메서드의 지역변수와 같은 데이터가 쌓였다가 쓰임이 다하면 휘발된다. 쌓이고 휘발되는 기준은 쉽게 말해 함수 단위, 어렵게 말해 스택 포인터 단위라고 할 수 있다. 힙 영역은 이전 질문의 ARC와 엮어서 할 말이 많은 파트다. 컴파일 타임에 크기를 알 수 없는 값들, 참조 타입들 등 쌓이면 무거워질 수밖에 없는 정보들인지라 영역의 크기도 런타임에 결정되며 이 '참조'를 적절히 쌓고 해제해주지 않으면 앱의 성능에 부정적인 영향을 미친다. 예를 들어, 어딘가에서 한 클래스를 런타임에 초기화하여 가지고 있다면 이 클래스의 데이터는 힙 영역의 빈 곳 어딘가에 할당된다. 이제 이 데이터의 주소가 여러 메시지에 의해 주고받아 지는 동안 '주소를 들고 있는 곳의 수'가 카운팅(개발자가 설계, ARC에 의해 관리) 되다가 0이 되는 순간 메모리에서 해제될 수 있게 되는데, 이를 잘못 예측·계산하여 상호참조 하는 등 불필요해졌는데도 어딘가에서 계속 참조하고 있거나 결코 참조를 멈추지 못하게 된다면 메모리 누수가 발생하는 것이다.

JasonLee0223 commented 2 months ago
1. iOS 앱의 메모리 구조(힙, 스택, 코드 영역 등)와 각 영역의 특징에 대해 설명해주세요.
Code - 우리가 작성한 소스 코드가 기계어(0, 1) 형태로 저장되며 컴파일 타임에 결정된다. 중간에 코드가 변경되면 안되기에 Read-Only 형태로 저장된다. --- Data - 전역 변수 및 staic으로 선언된 변수가 저장된다. 프로그램 시작과 동시에 할당되며 프로그램이 종료되어야 메모리가 해제된다. 실행 도중 값이 변경될 수 있어 Read-Write로 지정된다. --- Stack 함수 호출 시 함수의 지역변수, 매개변수, 리턴 값 등등이 저장되며 함수가 종료되면 저장된 메모리도 해제된다. 컴파일 타임에 결졍되기 때문에 무한히 할당할 수 없다. --- Heap 프로그래머가 할당 / 해제하는 메모리 영역 동적할당을 할 수 있으며 메모리 사용 후 메모리 해제를 해줘야 하기 때문에 휴먼 에러가 발생할 수 있다. 유일하게 런타임에 결정되기에 데이터 크기가 확실하지 않을 때 사용한다. 스위프트에서는 클래스, 클로저 같은 참조타입의 값이 자동 할당되며 ARC를 통해 자동으로 해제된다.
2. 힙 영역에서 객체가 어떻게 할당되고 관리되는지 설명해주세요.
힙 영역은 낮은 메모리 주소부터 할당 받게되며 ARC를 통해 클래스 인스턴스 생성 혹은 클로저를 생성하게 된다면 힙 영역에 객체가 할당됩니다. 물론 Delegate 패턴과 같은 방식을 사용하게되면 memory leak이 발생할 수 있기 때문에 weak 키워드를 적절하게 잘 사용해줘야합니다.
3. 스택 영역에서 함수 호출과 로컬 변수의 메모리 할당 및 해제 과정을 설명해주세요.
스택 영역은 힙 영역과 충돌하면 안되기에 높은 메모리 주소부터 할당받게됩니다. 우선 함수가 호출되면 해당 함수의 스택 프레임이 스택 영역에 추가됩니다. 스택 프레임에는 함수 매개변수, 복귀 주소, 이전 스택 프레임의 포인터가 저장됩니다. 함수 로컬 변수들도 스택 프레임에 함께 저장됩니다. 호출이 완료된 뒤 함수가 수행된 뒤 종료되면 스택 프레임이 스택에서 제거되며 스택 포인터가 감소하며 이전 스택 프레임으로 돌아갑니다. 이러한 호출과 완료과정은 LIFO 자료구조에 맞춰 설계되었다는 것을 확인할 수 있는 점입니다.