체인의정석

Rust Ownership , rust ownership을 이해하기 위한 기초 (heap, stack 알아보기) 본문

블록체인/Rust

Rust Ownership , rust ownership을 이해하기 위한 기초 (heap, stack 알아보기)

체인의정석 2023. 1. 16. 18:41
728x90
반응형

들어가기에 앞서

지난 시간에 우리는 러스트의 주요 컨셉인 statement와 Expression에 대해서 살펴보았다.

이번 시간에는 rust의 주요 개념인 ownership에 대해서 알아보도록 하겠다.

rust의 ownership을 사용하면 가비지 콜렉터 없이도 메모리의 안정성을 보장 받을 수 있다고 한다.

 

Ownership은 뭘까?

오너쉽은 러스트 프로그램이 메모리를 관리하는 규칙이라고 할 수 있다. 모든 프로그램들은 해당 프로그램이 컴퓨터의 메모리를 관리하는 법을 알아야 한다. 몇몇 언어들은 가비지 콜렉션이 지원되어서 더이상 안사용되는 메모리들을 정리해준다. 또는 유저들이 직접 할당을 하고 free 시켜주고 있다. 러스트는 제3의 길을 선택하였다!

 

그것은 바로 메모리를 오너쉽 시스템을 통해 관리하고 컴파일이 오너쉽 규칙을 체크해 주는 것이다. 만약 오너쉽 규칙이 위반된다면 컴파일이 적용되지 않는다. 

 

Stack과 Heap

많은 프로그래밍 언어들은 힙이나 스택을 매우 자주 고려할 필요가 없지만 러스트의 경우는 다르다. 스택과 힙은 코드가 실행되는 동안 사용가능한 메모리 유형이지만 다른 방향으로 구현되어 있다.

 

스택은 값들은 순서대로 저장하고 저장 순서와 반대로 삭제한다. 후입 선출 방법이다. 스택에 데이터를 넣는 것은 pushing onto the stack이라고 부르며 스택에서 데이터를 꺼내는 작업은 popping off the stack이라고 부른다. stack안에 저장된 데이터들은 명시되어 있으며 고정된 사이즈여야 한다.

 

중간의 데이터들을 꺼내거나 넣는것 또한 쉽지 않다. 가변 가능한 데이터를 다루는 작업은 heap에서 다루어야 한다.

 

요약하자면 "스택 = 후입선출 & 고정된 값들" 인 것이다.

 

힙의 경우는 조금 더 자유롭다. 힙에 데이터를 넣게 되면 메모리 할당자가 비어있는 공간을 찾은 후에 힙이 충분히 크다면 사용한다고 적으며 포인터를 리턴해준다. 포인터는 따라서 주소값이 나오게 된다. 이러한 과정은 allocating on the heap이라고 부른다. 또는 allocating (할당)이라고도 부른다. 포인터가 알려져 있고 고정 사이즈이기 때문에 스택에서는 포인터를 저장할 수 있으며 실제 데이터를 원할 때는 포인터를 따라서 가져올 수 있다.

요약하자면 "힙 = 포인터 & 가변 값들" 인 것이다.

 

당연히 스택에서는 포인터가 저장되어 있고 찾아오는 과정이 포함되어 있기 때문에 스택을 사용하는 것이 힙에 할당하는것 보다 속도가 빠르다. 반대로 힙을 사용하면 더 많은 작업을 사용하게 된다. 

 

또한 데이터에 접근할 때도 힙이 더 느리다. 포인터를 한번 거치기 때문이다.

코드에서 함수를 실행하게 되면 값들은 함수를 통해서 (힙에 있는 데이터에 대한 포인터)가 함수로 전달되게 되고 함수의 지역 변수들이 스택에 쌓이게 된다. 만약 함수가 끝나게 된다면 해당 값들은 스택에서 사라지게 된다.

 

이를 잘 파악해서 힙에서는 중복되는 데이터를 없애고 힙에서 안사용하는 데이터를 지워서 오너쉽의 주소들의 공간이 넘치지 않도록 관리를 하는 작업이 러스트에서 필요하게 되는 것이다. 오너쉽을 잘 알게 되면 스택이나 힙을 자주 기억할 필요는 없지만 힙과 스택에 대해서는 알아두는 것이 좋다.

 

Ownership 규칙

오너쉽 규칙을 먼저 살펴보자 

1. 러스트의 각 값들은 owner가 있다.

2. 한번에 하나의 owner만 존재한다.

3. owner가 범위 밖을 벗어나게 되면 값들이 삭제되게 된다.

 

Variable 가변 인자의 범위 Scope

fn main() {
    {                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    }                      // this scope is now over, and s is no longer valid
}

만약 s라는 let 인자값이 있다면 스코프 밖으로 가게되면 더이상 유효하지 않게 된다.

 

문자형 String의 경우

String의 경우에는 유용하지만 불변성이라는 특징과 모든 string 값들을 우리가 코드를 쓸 때 알 수 없다는 단점이 존재한다. 따라서 String은 힙에 할당되는 두번째 string type인 String을 사용하게 된다.

let s = String::from("hello");

 여기서 "::"를 사용하는 것은 string_from이라는 이름을 사용하는 대신 사용하는 것이다. 이러한 스트링의 경우

fn main() {
    let mut s = String::from("hello");

    s.push_str(", world!"); // push_str() appends a literal to a String

    println!("{}", s); // This will print `hello, world!`
}

이런식으로 변경이 가능하다. String은 힙으로 할당되어 변경이 가능하며 literal은 스택으로 할당되어 변경이 불가능 한 것이다.

 

 

메모리와 할당

string literal의 경우 컴파일시에 하드코딩 되어 있다는 것을 알 수 있어서 효율적이다. 하지만 이는 불변성의 특징 때문이며 사이즈가 프로그램이 돌아가며 가변성이 있는 문자열이라면? 언제 변경될지 모르는 사이즈의 문자열을 계속해서 메모리에 두고 프로그램을 돌릴 수는 없다. 

 

만약 string이 가변성을 갖기 위해서는 2가지가 요구된다.

1. 메모리 할당자가 코드를 실행할때 메모리가 요청되어야 한다.

2. 메모리에 대한 할당을 String을 사용하는 작업이 끝났을 때 메모리 할당자에게 돌려주어야 한다.

 

1번째 파트는 위의 String::from에서 이루어졌지만 2번의 파트는 다르다. 가비지콜렉터가 있는 언어에서는 가비지 콜렉터가 더이상 사용하지 않는 메모리를 삭제한다. 그리고 우리는 이에 대해 생각할 필요가 없다.

 

이를 가비지 콜렉터 없이 하려면 정확한 타이밍에 이를 호출해서 할당을 제거해 주어야 하기 때문에 상당히 어렵다고 한다. 하나의 allocate에 하나의 free를 짝을 지어 주어야 하는 것이 어렵기 때문이다.

 

러스트는 메모리가 자동적으로 scope를 벗어나면 할당이 해제되는 다소 간단한 방식을 사용한다. 

fn main() {
    {
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    }                                  // this scope is now over, and s is no
                                       // longer valid
}

이런식으로 string literal대신 string을 사용하게 되면 s 가 scope를 벗어날 때 자동적으로 할당이 된다고 한다.

 

스코프 밖을 벗어나면 drop이라는 할당을 반납하는 작업을 자동적으로 하게 되는데 간단해 보이지만 더 자세히 보면 복잡한 것을 알 수 있다.

 

출저 : https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html

 

Understanding Ownership - The Rust Programming Language

Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it’s important to understand how ownership works. In this chapter,

doc.rust-lang.org

 

728x90
반응형
Comments