2025년 4월 7일 월요일

3-2강 - 동적 메모리 할당

 Box 와 Rc를 사용해 동적 메모리를 할당 받을 수 있다.


1. Box

- Box::new : 메모리 할당

- * : 메모리에 접근 

fn main() {
    let mut x = Box::new(10); //Box를 사용하여 힙에 데이터를 할당
    println!("x: {}", x);  // x: 10

    *x = 20; // 할당받은 메모리에 접근 하려면 * 사용
    println!("x: {}", x); // x: 20
}

2. Rc(Reference-counting pointer) : 강한 참조
- Rc로 관리되는 데이터는 공유가 가능해서 여러 변수가 동일한 값을 참조할 수 있다.
- 데이터를 참조하는 모든 변수들이 메모리에 존재하면 값을 해체 하지 않는다.
- 참조 횟수를 추적해 참조 횟수가 0이 되면 데이터를 삭제한다.
- 순환 참조가 발생하면 참조 횟수가 0이 되지 않아서 메모리 누수 발생한다.
use std::rc::Rc;

struct Person {
    age: i32,
}

fn main() {
    // person을 공유 객체로 생성
    let person = Rc::new(Person { age: 10 });

    // person복제
    let p1 = person.clone();
    println!("person: {} p1: {}", person.age, p1.age);
    println!("RefCount: {}", Rc::strong_count(&person)); //+1
   
    // person복제
    let p2 = person.clone();
    println!("RefCount: {}", Rc::strong_count(&person)); //+1

    { //새로운 스코프 시작
        // person복제
        let p3 = person.clone();
        println!("RefCount: {}", Rc::strong_count(&person)); //+1
    } //스코프 종료 (그래서 p3는 소멸됨)

    println!("RefCount: {}", Rc::strong_count(&person)); //-1
}

3. RefCell
- Rc는 불변성을 가진 참조형이기에 공유 데이터를 변경할 수 없다.
- 공유 데이터를 수정하려고 하면 컴파일 오류가 발생한다.
- RefCell 은 변경 불가능한 변수를 임시로 변경 가능하게 해준다.
// RefCell<T>를 사용해 next 의 참조값을 변경하는 예제
use std::rc::Rc;
use std::cell::RefCell; //내부 가변성을 제공

struct Person {
    name: String,
    age: i32,
    next: RefCell<Option<Rc<Person>>>, // RefCell<>로 감싸서 next는 수정 가능
}

fn main() {
    // p1 노드 생성
    let mut p1 = Rc::new(Person {
        name: String::from("Luna"),
        age: 30,
        next: RefCell::new(None), //처음에는 다음 노드가 없음.
    });

    // p2 노드 생성
    let mut p2 = Rc::new(Person {
        name: String::from("Rust"),
        age: 10,
        next: RefCell::new(None), //처음에는 다음 노드가 없음.
    });

    // p1의 next 필드에 대한 가변 참조를 얻음
    let mut next = p1.next.borrow_mut();

    // * 키워드를 사용하면 RefCell<T>의 내부 값을 얻을 수 있다.
    *next = Some(p2.clone()); // p1뒤에 p2를 추가
}
 4. 약한 참조
- Rc는 순환참조가 발생하면 메모리 누수가 발생한다.
- Weak 를 사용하면 약한 참조를 만들어서 순환 참조 문제를 해결 가능하다.
// Rc와 Weak를 사용해서 순환 참조 문제를 해결하는 예제
use std::rc::{Rc, Weak};  // Rc: 강한 참조 / Weak: 약한 참조
use std::cell::RefCell;   // 내부 가변성을 위해 사용 (가변 borrow)

struct Person {
    id: i32,                          // Person 식별자 (id)
    next: RefCell<Option<Weak<Person>>>, // 다음 사람 참조 (약한 참조)
    // Weak<T> 를 사용하면 참조카운트를 증가시키지 않음
}

// Rc 참조카운트가 0이 되면 자동 호출되는 drop 함수 구현
impl Drop for Person {
    fn drop(&mut self) {
        // 메모리 해제될 때 어떤 Person이 drop 되었는지 출력
        println!("p{} Drop!", self.id);
    }
}

fn main() {
    // p1 객체 생성 (참조카운트 strong=1)
    let mut p1 = Rc::new(Person {
        id: 1,
        next: RefCell::new(None),
    });

    // p2 객체 생성 (참조카운트 strong=1)
    let mut p2 = Rc::new(Person {
        id: 2,
        next: RefCell::new(None),
    });

    // p1 -> p2 연결 (약한 참조로 연결)
    {
        let mut next = p1.next.borrow_mut();        // RefCell을 사용해 내부 가변 접근
        *next = Some(Rc::downgrade(&p2));           // Rc::downgrade() : Weak 참조 생성
        // Rc::downgrade를 사용하면 참조카운트 증가하지 않음 (순환참조 해결)
    }

    // p2 -> p1 연결 (약한 참조로 연결)
    {
        let mut next = p2.next.borrow_mut();
        *next = Some(Rc::downgrade(&p1));           // 약한 참조로 연결
    }

    // 현재 Rc 참조카운트 출력
    println!("p1 RefCount: {} p2: RefCount: {}",
        Rc::strong_count(&p1), Rc::strong_count(&p2));
    // 출력 예시
    // p1 RefCount: 1 p2 RefCount: 1

    // 약한 참조는 strong_count 증가 안되므로
    // main 스코프 종료시 Rc 카운트 1 -> 0 으로 떨어짐
    // -> Drop 호출됨
}
Box 와 Rc
- 외부 공유가 필요한 경우 Rc 사용, 그렇지 않다면 Box 사용
- 수정이 불가능한 Rc변수를 변경하려면 RefCell 기법도 사용

댓글 없음:

댓글 쓰기