메모리구조와 자료구조 이제 구분해야지....

sonny·6일 전
3

TIL

목록 보기
43/48
post-thumbnail

미루고 미루다 메모리 구조와 더불어 자료구조까지 공부 해보려한다.

메모리 구조는 프로그래밍 전반에 걸쳐 중요한 개념이라고 한다.

메모리 구조를 이해하면 코드의 효율성도 높아지고,

앱의 동작방식을 깊이 이해하는데도 큰 도움이 된다니 차근차근 공부해보자.

먼저 컴퓨터 메모리 구조는 크게 4가지 영역으로 나뉜다.

  • 코드 영역
  • 데이터 영역
  • 스택 영역
  • 힙 역역

코드 영역 (Code Segment)

코드영역 (Code Segment) 은 실행할 프로그램의 명령어가 저장이 되고,

읽기 전용의 특징이 있다.

int main() { 
    return 0; 
}

위의 main 함수 코드 자체는 코드 영역에 저장된다.

데이터 영역 (Data Segment)

데이터 영역의 역할로 프로그램 시작과 동시에 할당되는 전역 변수와 정적(static) 변수들이 저장된다.

프로그램이 종료 될 때까지 메모리에 남아있는 특징이 있다.

var globalVar = 10 // 데이터 영역에 저장

globalVar 은 프로그램 실행 내내 메모리에 존재한다.

스택 영역 (Stack Segment)

스택 영역은 함수 호출 시 생성되는 지역변수와 매개변수가 저장된다.

함수가 종료되면 메모리가 자동으로 해제 되며,

LIFO (Last In, First Out) 방식으로 작동한다.

스택 영역의 크기가 제한되어 있는 특징이 있는데,

이 말은 시스템에서 프로그램이 사용할 수 있는 스택 메모리의 크기가 고정되어 있다는 의미라고 보면 된다.

어떤 프로그램을 설계한다고 했을 때, 이 부분을 고려해서 재귀 호출이나 지역 변수 사용을 효율적으로 관리해야 하는데,

제한된 크기를 초과하게 되면 프로그램이 중단되거나,

스택 오버플로우가 발생할 수 있으니 주의가 필요할 수 있다고 한다.

func example() {
    let localVar = 5 // 스택 영역에 저장
}

localVar는 함수가 종료되면 사라진다.

힙 영역 (Heap Segment)

힙 영역은 프로그래머가 동적으로 할당하는 메모리가 저장되는 역할을 한다.

크기를 알 수 없는 데이터, 큰 배열이나 객체 등은 런타임에 힙 메모리에 저장된다고 한다.

힙 메모리는 메모리 공간을 비연속적으로 사용하기 때문에,

필요한 만큼 크기를 동적으로 요청할 수 있다.

그리고 swift에서는 자동 해제 지원이라고 해서 힙 메모리에 저장되는 데이터는 ARC를 통해 메모리 관리를 자동화 하고,

힙 메모리는 스택보다 크고 유연하지만 비효율적으로 관리될 경우 메모리 누수나 성능 문제가 발생할 수 있다.

이걸 예방하기 위해 강한 참조 순환 등을 피하는 설계가 필요하다고 한다.

class Example {
    var name: String
    init(name: String) {
        self.name = name
    }
}
let instance = Example(name: "Heap") // 힙 영역에 저장

Example 인스턴스는 힙 영역에 저장되고, instance는 스택에 있는 참조다.


swift에서 메모리 구조

Swift는 ARC(Automatic Reference Counting)를 사용하여 메모리를 관리한다.

메모리 구조와 Swift의 상호작용 방식을 한번 보자.

스택 vs 힙

1. 값 타입(Value Types)

  • 구조체(struct), 열거형(enum), 기본 데이터 타입(Int, Double)은 스택에 저장된다.
  • 값 자체를 복사해서 사용한다.
var a = 10
var b = a // b는 a의 값을 복사
b = 20
print(a) // 10

2. 참조 타입(Reference Types)

  • 클래스(class)는 힙에 저장된다.
  • 같은 메모리 주소를 참조한다.
class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}
let personA = Person(name: "sonny")
let personB = personA
personB.name = "gyeom"
print(personA.name) // gyeom (같은 힙 메모리 참조)

ARC (Automatic Reference Counting)

ARC는 힙 메모리를 자동으로 관리하는 시스템인데,

메모리를 해제할 시점을 ARC가 참조 카운트를 기반으로 판단한다고 한다.

이 때 참조카운트는 객체가 힙 메모리에 남아있을지 해제될지를 결정하는 핵심 요소로 볼 수 있다.

참조 카운트라는 말이 낯설다. 뭘 세는걸까

참조카운트는 특정 객체를 참조하는 변수나 상수의 개수를 뜻한다고 한다.

참초 카운트가 0이 되면 객체는 더 이상 필요하지 않기 때문에

ARC가 메모리를 해제한다.

ARC의 기본 동작 원리

  1. 객체 생성 시 참조 카운트 증가
    객체가 생성되면 참조 카운트는 1로 설정 됨.

  2. 참조가 추가될 때 증가
    다른 변수나 상수가 같은 객체를 참조하면 참조 카운트가 1씩 증가함.

  3. 참조가 제거될 때 감소
    객체를 참조하는 변수가 더 이상 사용되지 않으면 참조 카운트가 1씩 감소함.

  4. 참조 카운트가 0이 되면 메모리 해제
    객체를 참조하는 변수가 없으면 ARC가 메모리를 자동으로 해제함.

.
.
.

자, ARC 동작의 예시를 보자

class Person {
    var name: String
    init(name: String) {
        self.name = name
        print("\(name) 객체가 생성되었습니다.")
    }
    deinit {
        print("\(name) 객체가 메모리에서 해제되었습니다.")
    }
}

var person1: Person? = Person(name: "Alice") // 참조 카운트 1
var person2 = person1                        // 참조 카운트 2
person1 = nil                                // 참조 카운트 1
person2 = nil                                // 참조 카운트 0 -> 메모리 해제

실행 결과는

Alice 객체가 생성되었습니다.
Alice 객체가 메모리에서 해제되었습니다.
  • person1person2는 같은 객체를 참조한다.
  • person1nil이 되어도 person2가 여전히 객체를 참조하고 있으므로 메모리는 해제되지 않는다.
  • person2nil이 되면 참조 카운트가 0이 되어 ARC가 객체를 해제한다.

강한 참조 순환 (Strong Reference Cycle)

기본적으로 swift의 객체 참조는 강한 참조인데,

만약 두 객체가 서로를 강하게 참조하게 된다면

참조 카운트가 0이 될 수 없어서 메모리가 해제되지 않는 상황이 된다.

결과적으로 메모리 누수가 발생하게 되는 것이다.

강한참조 순환 예시를 보자

class Person {
    var name: String
    var friend: Person?  // 다른 객체를 참조하는 변수

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 객체가 해제되었습니다.")
    }
}

var alice: Person? = Person(name: "Alice")
var bob: Person? = Person(name: "Bob")

alice?.friend = bob  // Alice가 Bob을 강하게 참조
bob?.friend = alice  // Bob이 Alice를 강하게 참조

alice = nil  // Alice를 nil로 설정해도
bob = nil    // Bob을 nil로 설정해도
// 둘 다 메모리에서 해제되지 않는다! (강한 참조 순환)

왜 해제되지 않나요?

  • alicebob을 참조하고, bob은 다시 alice를 참조한다.
  • 이로 인해 alice와 bob의 참조 카운트가 각각 0이 되지 않기 때문에, 두 객체는 계속해서 메모리에서 살아있게 된다.
  • 그래서 alice = nilbob = nil을 해도 참조 카운트가 1로 유지되고, 두 객체는 메모리에서 해제되지 않는다.

강한 참조 순환 해결 방법

강한 참조 순환을 방지하려면,

약한 참조(weak)나 비소유 참조(unownde)를 사용해야한다.

이들 참초는 카운트를 증가시키지 않기 때문에 객체가 해제될 수 있다.


약한 참조 (Weak Reference) 사용

class Person {
    var name: String
    weak var friend: Person?  // 약한 참조로 설정

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 객체가 해제되었습니다.")
    }
}

var alice: Person? = Person(name: "Alice")
var bob: Person? = Person(name: "Bob")

alice?.friend = bob  // Alice가 Bob을 약하게 참조
bob?.friend = alice  // Bob이 Alice를 약하게 참조

alice = nil  // Alice를 nil로 설정
bob = nil    // Bob을 nil로 설정 -> 메모리에서 해제됨!

결과적으로

  • alicebob 모두 약한 참조로 설정되어 있기 때문에, 서로를 참조한다고 해도 참조 카운트가 증가하지 않는다.
  • alicebobnil로 설정되면 각각 참조 카운트가 0이 되어 메모리에서 정상적으로 해제된다.

정리하자면,

강한 참조 순환은 객체들이 서로를 강하게 참조할 때 발생한다.

이로 인해 객체들이 메모리에서 해제되지 않아서 메모리 누수가 발생해버리는 것인데,

이를 해결하려면 약한 참조(weak)비소유 참조(unowned)를 사용하여 참조 카운트를 증가시키지 않게 해야 한다는 걸 잊지말자.


이렇게 메모리 구조에 대해 알아보았다.

이제 자료구조와의 차이를 좀 공부해보고 싶다.

메모리 구조와 자료구조는 모두 프로그래밍과 컴퓨터 과학에서 중요한 개념이지만,

초점과 목적이 다르다고 한다.

1. 메모리 구조 (Memory Structure)

메모리 구조의 정의

메모리 구조는 컴퓨터 메모리가 데이터를 저장하고 관리하는 방식을 말하는데,

프로그램 실행 중 데이터가 어디에, 어떻게 저장되는지를 다룬다.

메모리 구조의 초점

컴퓨터 메모리의 물리적/논리적 구성을 이해하는데 중점을 두고,

메모리의 영역과 데이터를 저장하는 방식이다.(스택,힙,코드,데이터 등)

메모리 구조의 목적

프로그램의 메모리 효율성을 높이고 오류 (메모리 누수 등) 를 방지하며,

메모리 사용 방식 (할당, 해제)을 이해하고 최적화 하는 것이 목적이다.

메모리 구조의 예시

  • 스택(Stack): 함수 호출 시 지역 변수를 저장하고 함수가 종료되면 자동 해제.
  • 힙(Heap): 동적 메모리 할당을 위해 사용. 프로그래머가 수동 관리하거나, Swift의 ARC처럼 자동 관리.
  • 코드(Code): 프로그램 명령어가 저장됨.
  • 데이터(Data): 전역 변수, 정적 변수 등이 저장됨.

2. 자료 구조 (Data Structure)

자료 구조의 정의

자료구조는 데이터를 효율적으로 저장하고 조작하는 방법을 말한다.

어떤 데이터를 어떻게 구성하고 저장할지에 대한 설계 방식이라 보면 된다.

자료 구조의 초점

데이터의 구조와 관계를 정의하고,

데이터를 저장, 검색, 삽입, 삭제 등의 연산을 효율적으로 수행하는 방법에 초점을 둔다.

자료 구조의 목적

데이터의 효율적 관리와 알고리즘 성능의 최적화,

그리고 대량의 데이터를 다룰 때 연산 속도를 개선하는 것이 목적이다.

자료 구조의 예시

  • 배열(Array): 데이터가 연속된 메모리에 저장됨.
  • 연결 리스트(Linked List): 노드가 포인터로 연결됨.
  • 스택(Stack): LIFO 방식의 데이터 구조.
  • 큐(Queue): FIFO 방식의 데이터 구조.
  • 트리(Tree): 계층적 데이터 표현, 예: 이진 탐색 트리.
  • 그래프(Graph): 정점과 간선으로 데이터 표현.
  • 해시 테이블(Hash Table): 키-값 쌍으로 데이터 저장.

둘의 관계

자료구조는 메모리구조를 활용해서 구현이 된다고 한다.

예로 들면, 배열은 메모리에서 연속된 공간에 데이터를 저장하고,

연결 리스트는 메모리의 비연속적인 공간을 포인터로 연결한다.

그리고 메모리구조는 자료구조의 성능에 영향을 미치는데,

예로 들면 스택과 큐는 주로 스택 영역을 사용하고,

동적 자료 구조는 힙 영역을 활용하는 것이다.

결론

  • 메모리 구조는 데이터를 저장하고 관리하는 물리적 기반을 다룬다.
  • 자료 구조는 데이터를 효율적으로 다루기 위한 추상적 설계다.
  • 자료 구조를 설계할 때 메모리 구조를 이해하면 더 최적화된 코드를 작성할 수 있다.

비교 표

특징메모리 구조자료 구조
초점데이터가 메모리에 어떻게 저장되고 관리되는지데이터를 어떻게 구성하고 조작할지
관점컴퓨터 시스템 관점알고리즘 및 문제 해결 관점
단위코드, 데이터, 스택, 힙배열, 리스트, 트리, 그래프, 해시 등
목적메모리 최적화 및 관리데이터 처리 및 알고리즘 성능 개선
사용되는 대상컴퓨터 메모리데이터

음...

메모리 구조는 프로그램의 성능과 안정성에 큰 영향을 미친다는 것을 깨달은 것 같다.

이제 메모리 관리가 잘못되면 프로그램이 예기치 않게 크래시가 나거나, 메모리 누수로 인해 성능 저하가 발생할 수 있다는 것도 알게 되었고,

자료 구조와 메모리 구조는 서로 밀접하게 연관되어 있어서

예를 들어, 배열은 연속된 메모리에 데이터를 저장하고,

연결 리스트는 비연속적인 메모리를 사용한다는 점에서 메모리 관리 방식이 다르다는 것도 공부했다.

그리고 애증의 ARC에 대해 그나마 자세히 알게 된 시간이었는데,

강한 참조 순환을 피하기 위한 방법도 어느정도 알 것 같다.

약한 참조나 비소유 참조를 사용하면 메모리 누수를 방지할 수 있다는 점이 중요하다는 걸 알았으니 이 정도면 공부 잘 한 거겠지..?!

profile
iOS 좋아. swift 좋아.

0개의 댓글