Programming

Rust기초 알기 - 4.4 소유권과 메모리 관리

IT오이시이 2023. 6. 30. 05:14
728x90

 

 

Rust기초알기 (4) - 소유권 시스템

4.1 소유자와 대여자 개념
4.2 소유권 규칙
4.3 소유권 이전과 복사
4.4 소유권과 메모리 관리

[목차] Rust Programming - Rust 기초 알기 목차.Zip

#Rust프로그래밍  #Rust언어기초 #Rust기초문법 #Rust기초알기

 

Rust기초 알기  - 4.4 소유권과 메모리 관리

 
 
러스트(Rust)의 소유권 시스템은 메모리 관리를 보장하기 위한 중요한 개념입니다. 이 시스템은 컴파일 시간에 안전성을 검증하면서 메모리 안정성과 런타임 오버헤드 없이 자원을 효율적으로 관리합니다. 아래에서 Rust의 소유권과 메모리 관리에 대해 설명하겠습니다.
 
러스트(Rust)의 주요 메모리 관리 기능으로 소유권 시스템, 빌림(Borrowing) 규칙 및 라이프타임 을 통해서 안정성과 메모리 효율성을 보장을 합니다.

  • 러스트 소유권 시스템
  • 빌림(Borrowing) 규칙
  • 라이프타임 

 

소유시스템과 빌림 규칙으로 메모리 관리

소유권 시스템으로 변수는 변수는 항상 특정 시점에서 하나의 소유자만 가질수 있도록 하고, 소유자는 변수의 수명을 관리하고, 더 이상 필요하지 않을 때 메모리를 해제 할 수 있습니다.  소유자의 범위를 벗어나면 명시적인 메모리 해제 없이 메모리가 해제 됩니다.
빌림 규칙은  소유자가 아닌 다른 변수에 대한 빌림을 사용하여 소유권을 이전 하지 않고 임시로 메모리를 대여하여 변수에 대한 참조를 생성할 수 있는 방법입니다. 가변 빌림과 불변 빌림 과 같은 방식으로 변수의 소유 관계를 추적하여  컴파일시 오류를 찾을 수 있습니다.
 


Null 포인트와 Dangling 포인트 방지

Rust 는 널(Null) 포인터와 댕글링(Dangling) 포인터 회피와 같은 일반적인 메모리 관리 오류를 방지하도록 메모리의 수명을 검사합니다. Rust는 참조하는 포인터가 항상 유효한 값을 가지도록 보장하여 널(Null) 포인터가 발생할 수 없도록 합니다. 또  댕글링 포인터와 같이 참조하는 메모리가 해제된 후에도  메모리를 참조하는 변수가 되지 않도록 변수의 유효한 수명을 검사하여  유효하지 않은 포인터를 사용하지 않도록 보장합니다.
*댕글링 포인터(Dangling Pointer) 는 참조하는 포인터가 여전히 해제되어 유효하지 않은 메모리 영역을 가리키고 있는 경우 입니다. 


댕글링 포인터 이용시  문제점 

  • 메모리 접근시 예측 불가능한 동작
  • 메모리 접근 불가 시 Segmentation fault
  • 잠재적인 보안 위험

 
 Rust 코드에서 댕글링 포인터의 예시

fn main() {
    let dangling = create_dangling(); // 댕글링 포인터 생성
    println!("Dangling pointer: {:?}", dangling);
} // 함수 블록을 벗어나면 dangling 변수는 소멸됨

fn create_dangling() -> &String {
    let s = String::from("Hello"); // 새로운 String을 할당
    &s // String의 참조자를 반환
}

----------------------------------------------------------
error[E0106]: missing lifetime specifier
 --> main.rs:4:29
  |
4 | fn create_dangling() -> &String {
  |                             ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value,
    but there is no value for it to be borrowed from

 

  •  create_dangling ()함수는 String 타입의 새로운 문자열을 생성하고, 
  • 그 문자열에 대한 참조자(&String)를 반환합니다.
  • 그러나 함수가 끝나면 s 변수는 스택에서 제거되므로 해당 문자열은 더 이상 유효하지 않게 됩니다. 
  • 따라서 dangling 변수는 댕글링 포인터가 됩니다.

 

 

Rust의 널(Null)포인트 방지


널 포인터를 방지하기 위해 명시적으로 값이 없는 상황을 처리할 수 있습니다. 함수의 리턴 값에 Option 타입을 사용합니다.
Rust의  Option<&String>은 Option은 열거형으로  값의 존재 여부를 나타내는 타입입니다. Option은 값이 존재하는 경우와 없는 경우 "None"를 미리 가지고 있어서  컴파일러가 널 포인터 예외를 방지하는 데 도움을 줍니다.

  •  find_value_by_key 함수는 주어진 HashMap에서 특정 키의 값을 찾아 반환합니다.
  •  값이 존재하지 않을 수도 있으므로, 반환 타입을 Option<&String>으로 지정하여 옵셔널 값으로 감싸 반환합니다.
  • Option<&String>을 사용하여 HashMap에서 특정 키의 값을 찾는 경우, 값이 존재하면 Some(&String)으로 값을 반환하고, 값이 없으면 None을 반환합니다. 
// Option<&String>은 &String 타입에 대한 옵션 값을 가질 수 있음을 나타냅니다.
// 따라서 해당 변수는 Some(&String) 또는 None 중 하나의 값을 가질 수 있습니다.
fn find_value_by_key(map: &std::collections::HashMap<i32, String>, key: i32) -> Option<&String> {
    map.get(&key)
}

fn main() {
    let mut map = std::collections::HashMap::new();
    map.insert(1, String::from("Value 1"));
    map.insert(2, String::from("Value 2"));

    let key = 3;
    let value = find_value_by_key(&map, key);

    match value {
        Some(v) => println!("Value found: {}", v),
        None => println!("Value not found"),
    }
}

 

  • main 함수에서는 HashMap을 생성하고,
  •  find_value_by_key 함수를 사용하여 특정 키의 값을 찾습니다. 
  • value 변수는 Option<&String> 타입이므로, match 표현식을 사용하여 값을 처리합니다. 
  • 값이 존재하는 경우 Some 분기로 들어가서 값을 출력하고, 
  • 값이 없는 경우 None 분기로 들어가서 "Value not found"를 출력합니다.

 
 

러스트(Rust)의 변수 수명 (Lifetimes):

 러스트는 참조자의 수명을 추적하여 메모리 안전성을 보장하는 수명 시스템을 가지고 있습니다. 참조자는 수명이 유효한 범위를 가지고 있습니다. 컴파일러는 수명 검사를 통해 참조자가 올바른 범위에서만 사용되도록 보장합니다. 이를 통해 댕글링 포인터와 같은 런타임 오류를 방지할 수 있습니다.

fn main() {
    let value = 10;  // (1) 변수 value 생성
    
    {
        let reference = &value;  // (2) value의 불변 참조 생성
        
        // (3) reference를 사용하여 작업 수행
        
    }  // (4) reference의 수명이 끝나면서 범위를 벗어남
    
    // (5) value를 사용하여 다른 작업 수행
    
}  // (6) value의 수명이 끝나면서 범위를 벗어남

 

  1.  main 함수 내에서   value = 10 변수가 정의되었습니다. 
  2. 블록({})을 사용하여 새로운 범위를 생성합니다.
  3. 블록 내에서 reference 변수는 value 변수의 불변 참조를 생성합니다.
  4. reference 변수는 value의 값을 읽을 수 있지만, 변경할 수는 없습니다.
  5. reference 변수를 이용하여 작업 을 수행 합니다.
  6. reference 변수의 수명은 블록 내에서 시작하여 블록의 끝에서 끝납니다.
  7. value 변수를 사용하여 작업을 수행 가능합니다.
  8. value도 main 블럭이 끝나면 수명이 끝납니다.

 

fn main() {
    let value = 10;  // (1) 변수 value 생성
    
    {
        let reference = &value;  // (2) value의 불변 참조 생성
        
        // (3) reference를 사용하여 작업 수행
        let value2 = value;
        println!("value: {}, reference: {}, value2: {}", value, reference, value2);
        
    }  // (4) reference의 수명이 끝나면서 범위를 벗어남
    
    // (5) value를 사용하여 다른 작업 수행
    println!("value: {}", value);
    
}  // (6) value의 수명이 끝나면서 범위를 벗어남

 

  •  value2는 (3)의 범위 내에서만 유효한 변수이며, 
  • (5)에서는 value2를 사용할 수 없습니다. 
  • 하지만 value는 (5)에서 여전히 유효하며 출력할 수 있습니다.
  • 마찬가지로 (6)에서 value, reference, value2를 사용할 수 없습니다.

 
Rust언어의 메모리 관리 시스템
Rust의 소유권과 Borrow 
 

728x90
반응형