티스토리 뷰

Rust-Language

소유권(Ownership)

kmj24 2021. 4. 2. 01:06

소유권이란? 

메모리를 관리하는 Rust언어 특성이다.

C/C++은 메모리를 직접 할당하고 해제하거나 Java, C# 등은 GC를 이용하여 메모리를 관리한다.

Rust는 소유권이라는 특성으로 메모리 관리가 가능하다.

 

소유권의 규칙

1. Rust의 각각의 값은 해당 값의 owner라고 불리는 변수를 가지고 있다.

2. 한번에 딱 하나의 owner만 존재할 수 있다.

3. owner가 scope 밖으로 벗어날 때 값은 버려진다.

 

소유권의 예제

1. 변수의 스코프

아래의 함수를 실행해보면 "hello"가 출력된다.

스코프 내에 있는 변수의 유효범위는 변수가 선언된 시점부터 스코프의 밖으로 벗어날 때 까지 지속된다.

이는 타 프로그래밍 언어와 비슷하다.

pub fn ownership(){
    let s = "hello";
    {
        let s = "?"; //s의 유효범위 ◀, 여기서부터 유효범위
        //◀
        //◀
        //◀
        //◀
    }	//여기서는 유효하지 않음
    println!("{}", s);
}

 

String타입의 소유권

pub fn ownership(){
    let mut s = String::from("test1");
    s.push_str(", test2");
    println!("{}", s);
}

String타입은 변경이 가능하고 데이터의 크기가 커질 수 있는 text 데이터를 지원하기 위해 만들어 졌고, 힙(사용자가 직접 메모리를 관리하는 메모리)에서 컴파일 타임에 알 수 없는 어느 정도 크기의 메모리 공간을 할당받아 저장할 필요가 있다.

 

1. 런타임에 OS로부터 메모리가 요청되어야 한다.

2. String 사용이 끝났을 때 OS에 메모리를 반납할 방법이 필요하다.

 

2번의 OS에 메모리를 반납할 때 GC를 가지고 있는 언어는 메모리 반납에 대한 고려를 하지 않아도 된다.

하지만 GC가 없으면, 개발자가 직접 할당받은 메모리가 더 필요없는 시점에 반납을 해야된다. 하지만 종종 메모리 해제를 잊게된다면 메모리가 낭비되고, 메모리를 너무 빨리 해제하면 유효하지 않은 변수를 갖게 되거나 반납을 중복으로 하게 되면, 프로그램의 버그가 발생한다.

 

Rust는 변수가 스코프를 벗어나는 순간 자동으로 메모리가 해제된다. (자동으로 drop을 호출)

 

변수 s1의 값을 변수 s2에 대입하는 상황이다.

pub fn ownership(){
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s2);
}

String데이터 타입은 메모리의 스택과 힙에 각각 저장된다

형태로 보면 문자열의 내용물을 담고 있는 메모리의 포인터, 길이, 용량은 스택에 저장되고, 문자열 변수에 할당된 데이터는 힙에 저장된다.

s2에 s1의 값을 대입할 경우 메모리 할당 형태는 이렇게 된다.

여기서 변수가 스코프를 벗어날 때 문제가 될 수 있다.

자동으로 s1과 s2에 대한 drop이 이루어 지려고 하는데 하나의 힙 메모리에 대하여 2번 drop을 호출하게 하려고 하기 때문이다.(double free 오류) 메모리를 2번 해제하는 것은 메모리 손상의 원인이 되고, 보안상의 취약점이 될 수 있다.

 

Rust는 메모리의 안정성을 보장하기 위해, s2에 s1의 값이 대입 되었을 경우 s1은 더이상 유효하지 않다고 간주 한다.

예를 들어 아래의 코드를 실행시킬 경우,

pub fn ownership(){
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);
}

위의 사진과 같은 오류가 발생한다.

이는 Rust의 소유권의 특징이다.

s2변수에 s1을 대입, 여기서는 대입이 아니라 이동이라고 표현을 하자.

s1의 소유권이 s2로 이동된 것이다.

즉, 변수 s1의 메모리공간은 s2로 소유권이 넘어간 것이다.

스코프가 끝날때 마다 메모리를 어떤 변수가 소유했는지만 파악하면, 손쉽게 메모리를 해제할 수 있다.

 

만약 String의 스택 데이터 뿐만 아니라 힙 데이터 복사(깊은복사)를 원한다면 clone이라는 공용 메소드를 사용할 수 있다.

pub fn ownership(){
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}, {}", s1, s2);
}

이럴 경우 s2에 s1의 값을 대입하더라도 두 변수 모두 호출할 수 있다.

여기서 우리는 clone을 호출했을 경우, 어떤 비용이 많이 들어갈지도 모르는 코드가 실행 중 이라는 것을 시각적으로 알 수 있다.

 

함수에서의 상황을 보자

pub fn ownership(){
    let mut s = String::from("test1");
    take_ownership(s);
    println!("{}", s);
}

fn take_ownership(some_string: String){
    println!("{}",some_string );
}

함수에서의 상황도 같다.

변수 s를 함수 take_ownership에 인자값으로 던져줬고 그와 동시 s의 소유권은 take_ownership의 parameter로 넘어가게 되므로 take_ownership함수를 호출한 뒤 변수 s는 사용할 수 없다.

이러한 소유권은 함수의 반환값에도 동일하게 적용된다.

 

하지만 정수자료형, char형 과 같은 메모리의 스택 영역에만 할당되는 데이터 타입에 대하여는 신경 쓸 필요가 없다.

하지만 문자열과 같은 데이터타입의 경우 크기가 쉽게 변하기 때문에 힙 영역에 메모리를 할당할 필요가 있으며, 메모리 관리가 필요하여 소유권이 존재한다.

 

이 장의 전체 코드

pub fn ownership(){
    let s = String::from("test1");
    //s.push_str(", test2");
    //println!("{}", s);

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}, {}", s1, s2);

    take_ownership(s);
    println!("{}", s1);
}

fn take_ownership(some_string: String){
    println!("{}",some_string );
}

 

 

참고 : rinthel.github.io/rust-lang-book-ko/ch04-01-what-is-ownership.html

 

소유권이 뭔가요? - The Rust Programming Language

러스트의 핵심 기능은 바로 소유권입니다. 이 기능은 직관적으로 설명할 수 있지만, 언어의 나머지 부분에 깊은 영향을 끼칩니다. 모든 프로그램은 실행하는 동안 컴퓨터의 메모리를 사용하는

rinthel.github.io

'Rust-Language' 카테고리의 다른 글

Slices  (0) 2021.04.05
참조자와 빌림  (0) 2021.04.03
제어문  (0) 2021.03.30
함수  (0) 2021.03.30
데이터타입  (0) 2021.03.30
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함