관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 3. Swift 메모리 할당 - 타입별 메모리 할당 위치 분석 심화 (String타입, struct안에 class, class안에 struct) 본문

iOS 응용 (swift)

[iOS - swift] 3. Swift 메모리 할당 - 타입별 메모리 할당 위치 분석 심화 (String타입, struct안에 class, class안에 struct)

jake-kim 2023. 8. 13. 01:53

1. Swift 메모리 할당 - address 확인 방법, withUnsafePointer, Memory Graph Debugger, vmmap

2. Swift 메모리 할당 - 타입별 메모리 할당 위치 분석 기본 (stack, heap)

3. Swift 메모리 할당 - 타입별 메모리 할당 위치 분석 심화 (String타입, struct안에 class, class안에 struct)

4. Swift 메모리 할당 - MALLOC, MALLOC guard, dyld private memory, Shared memory, ColorSync (vmmap PID)

메모리 어디에 할당되는지 확인 방법 복습

  • 1번 글, 2번 글에서 살펴본대로 아래 함수로 각 프로퍼티가 어떤 메모리에 할당되는지 확인이 가능
@inlinable func printMemoryAddress<T>(_ o: inout T) {
    withUnsafePointer(to: &o) { print($0) }
}
  • vmmap(virtual memory) 명령어를 사용하여 stack 메모리 파악
    • vmmap <PID> | grep Stack를 사용하여 각 스레드 별 stack의 메모리 범위 파악이 가능
    • 위에서 얻어온 메모리 위치가 이 stack 메모리 범위안에 속한다면 stack 영역에 할당되었다고 판단
    • (자세한건 이전 포스팅 글 에서 Int 타입 프로퍼티의 메모리 할당 위치 파악 참고)

String 타입의 메모리 할당 위치

  • string 프로퍼티 메모리 확인
var stringValue = "a"
printMemoryAddress(&stringValue) // 0x000000016b8038c8
  • 현재 실행중인 PID 확인

  • stack 메모리 범위 확인
vmmap 61277 | grep Stack

확대해서 보면, 5개의 Stack 메모리 공간이 존재

  • stringValue 프로퍼티의 주소는 16b8038c8이므로 thread 0에 할당됨을 확인가능

String타입인 copy by value 메모리 주소

  • 이전 예제보다 긴 문자열 하나와 copy by value한 프로퍼티 준비
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var stringValue = "iOS 앱 개발 알아가기 - string은 과연 stack에 저장될까? heap에 저장될까?"
        var stringValue2 = stringValue
        
        printMemoryAddress(&stringValue) // 0x000000016b8a78c8
        printMemoryAddress(&stringValue2) // 0x000000016b8a78b8
    }
}
  • Stack 메모리 범위 확인
vmmap 61462 | grep Stack

  • 첫번째 Stack 영역에 stringValue(16b8a78c8)가 속하는 것 확인
  • 두번째 Stack 영역에 stringValue(16b8a78b8)도 속하는 것 확인

struct 타입 메모리 주소

  • 동일하게 메모리 주소를 확인
struct StructA {
    let a = 0
}

var structValue = StructA()
printMemoryAddress(&structValue) // 0x000000016af9f8d0

  • 예상한대로 Struct는 value type이므로 0번 Stack 영역에 할당

class 타입

  • 동일하게 메모리 주소를 확인
class ClassA {
    let a = 0
}

var classInstance = ClassA()
printMemoryAddress(&classInstance) // 0x000000016b97b8c0

  • class는 reference type인데도 Stack 영역에 저장됨을 확인 
    • heap에 할당되지 않는 이유는?
    • 주의) 위에서 살펴본 주소는 classInstance의 주소가 아닌, 참조의 주소를 본 것
    • reference type 메모리 관리는 stack영역에 reference 주소를 저장하고, 실제 주소는 heap에 저장됨을 유의
func printHeapMemoryAddress<T>(_ o: T) {
    let pointer = UnsafeMutableRawPointer(mutating: Unmanaged.passUnretained(o as AnyObject).toOpaque())
    print(pointer)
}

printHeapMemoryAddress(classInstance) // 실제 주소: 0x0000000128956c50

  • Stack영역에 주소가 존재하지 않는다는 것을 확인
    • Heap영역에 할당된 메모리 주소 영역을 확인해야 하므로 grep vmmap | grep MALLOC 으로 확인
vmmap 62250 | grep MALLOC
  • 중간에 MALLOC_TINY라는 곳의 범위에 속하는 것 확인이 가능
    • TINY개념은 다음 포스팅 글에서 계속

class타입을 가지고 있는 struct 타입 메모리 주소

  • 동일하게 메모리 주소를 확인
class ClassA {
    var a = 0
}

struct StructNestedClass {
    var a = ClassA()
}

var structValueNestedClass = StructNestedClass()
printMemoryAddress(&structValueNestedClass) // 주소: 0x000000016dcbb8d0
printMemoryAddress(&(structValueNestedClass.a)) // reference 주소: 0x000000016dcbb8d0
printHeapMemoryAddress(structValueNestedClass) // (struct는 class타입이 아니므로 crash)
printHeapMemoryAddress(structValueNestedClass.a) // 주소: 0x0000000153c26710
  • vmmap을 통해 stack영역 메모리 확인
    • structValueNestedClass 객체는 첫번째 스택 영역범위에 속하므로, Stack영역에 저장
    • structValueNestedClass.a 참조 타입 메모리 주소 역시도 Stack영역에 저장
    • structValueNestedClass.a 값 자체는 예상한대로 Stack영역에 저장되지 않음

  • Heap 영역을 확인
    • structValueNestedClass.a 값 자체는 Heap 영역에 할당된것을 확인
vmmap 63090 | grep MALLOC

struct타입을 가지고 있는 class 타입 메모리 주소

  • 동일하게 메모리 주소를 확인
class ClassNestedStruct {
    var a = StructA()
}

struct StructA {
    let a = 0
}

var classNestedStruct = ClassNestedStruct()
printMemoryAddress(&classNestedStruct) // 주소: 0x000000016b5cb8c0
printMemoryAddress(&(classNestedStruct.a)) // 주소: 0x000000014e14fcc0
printHeapMemoryAddress(classNestedStruct) // 주소: 0x000000014e14fcb0
printHeapMemoryAddress(classNestedStruct.a) // (crash: a는 struct이므로)
  • 메모리 할당 확인
    • class 인스턴스 참조 메모리 위치는 예상대로 Stack에 할당

stack 범위

  • class 인스턴스안에 있는 struct 타입 변수(classNestedStruct.a)는 heap에 할당
  • class 인스턴스는 예상대로 heap에 할당

  • 알게된 점
    • class 인스턴스안에 있는 struct 타입 변수(classNestedStruct.a)는 heap에 할당
  • struct타입 변수가 곧바로 heap에 할당되는 이유?
    • struct를 감싸고 있는 class는 동적으로 메모리를 관리할 수 있는 reference 타입이므로 struct도 reference 타입처럼 관리되는 것
    • class 인스턴스 안에 있는 struct 타입 변수는 class처럼 참조 타입이 존재하지 않으므로, stack에 참조 타입을 두지 못하고 곧바로 heap영역에 저장시키는 것

정리

  • String 타입은 Stack 영역에 할당
  • class 타입 인스턴스의 참조값은 stack영역에 할당되고, 인스턴스 자체 값은 heap에 할당
    • class 타입 인스턴스의 참조값 주소를 출력하는 함수와, class 타입 인스턴스 자체의 주소를 출력하는 함수는 다르므로 주의
  • class타입을 가지고 있는 struct 타입 메모리 주소는 stack에 할당
    • struct 안의 class 인스턴스 reference 주소는 stack에 할당
    • struct 안의 class 인스턴스 값 자체 주소는 heap에 할당
  • struct타입을 가지고 있는 class 타입 메모리 주소는 heap에 할당
    • class 인스턴스 안에 있는 struct 타입 변수는 heap에 할당
    • struct는 reference type(class타입)처럼 stack에 참조 주소를 별도로 두지 못하기 때문에, 곧바로 heap에 실제 타입을 두는 것

* 전체 코드: https://github.com/JK0369/ExMemoryCheck

* 참고

https://gist.github.com/godrm/a2bff14ed36a98aa62f934a6ad426617#file-memorydump-swift

 

 

Comments