개발_기록용

RUST 스터디[RUST vs C++] 19. Memory Management 본문

RUST 스터디

RUST 스터디[RUST vs C++] 19. Memory Management

나폴나폴 2024. 6. 18. 12:17
728x90

https://google.github.io/comprehensive-rust/memory-management.html

 

Memory Management - Comprehensive Rust 🦀

This segment should take about 1 hour. It contains: SlideDuration Review of Program Memory5 minutes Approaches to Memory Management10 minutes Ownership5 minutes Move Semantics5 minutes Clone2 minutes Copy Types5 minutes Drop10 minutes Exercise: Builder Typ

google.github.io

 

- Rust에서 가장 중요한 봉우리 한 개.

 

19.1 Review of Program Memory


Stack : Continuous area of memory for local variables

: 연속된 곳에 위치하므로 속도가 빠르고, locality가 좋다.

 

- Values have fixed sizes known at compile time.

  • Extremely fast : just move a stack pointer. => 연속된 곳에 위치하니 포인터를 움직여주기만 하면 됨.
  • Easy to manage : follows function calls.      => 연속된 곳에 위치하니 관리 쉬움.
  • Great memory locality : locality는 [ 공간적 지역성 / 시간적 지역성 ] 이 두가지를 갖는다.
🚥 공간적 지역성 : 데이터가 특정 영역에 모이는 성질. (사용된 데이터의 인접 데이터가 쓰일 가능성이 큰 성질.)
⏰ 시간적 지역성 : 데이터의 읽기/쓰기를 위해 특정 메모리가 쓰였을 때 이 메모리가 가까운 시일 내에 다시 쓰일 가능성이 큰 성질.

 

Heap : Storage of values outside of function calls.

- Values have dynamic sizes determined at runtime.

  • Slightly slower than the stack : some book-keeping needed.
  • No guarantee of memory locality.
String의 경우, 실제 값은 Heap에 저장되고, capacity, pointer, length 같은 메타데이터는 Stack에 저장된다.

 

19.2 Approaches to Memory Management

메모리 관리 방법은 두개가 있다.

  • 하나는 프로그래머가 정권을 쥐고, 직접 힙 메모리를 언제 할당할지를 직접 결정.

- C나 파스칼 같은 것.- 어떤 메모리가 유효한지 아닌지도 직접 개발자가 처리해야 하므로, 실수가 일어날 여지가 많다.ex. 할당 해제된걸 사용하려 하는 경우.

 

  • 런타임에서 메모리를 자동으로 할당.

- Go, 자바, 파이썬 같은 것.- 레퍼런스 카운팅이나 가비지 컬렉터 등이 쓰이지 않는 메모리가 할당 해제되는 시스템으로 동작.

 

Rust는 이 두가지를 혼합한 형태이다.프로그래머가 정권을 쥐면서도, 안전하게 할 수 있는 형태.

 

19.3 Ownership

모든 변수들은 각각의 Scope를 갖는다. Scope는 어떤 값을 쓸 수 있는 지의 유효한 범위.

그래서 이 Scope 밖에서 쓰려면 오류가 남.

위와 같이 C++에서 하면 복사가 되기 마련이다. s2는 s1을 복사한 값을 가질 뿐, s1과 s2는 다르다.

 

근데, Rust에선 s1을 출력하려 하면 오류가 난다. 왜?

이미 s1이 s2로 이동되어, s1의 값이 없어진 것이다.

s1의 소유권이 s2로 넘어간 것!

 

19.4 Move Semantics

이제 s1은 빈 껍데기라 쓸 수 없다. 이게 C++과 완전히 다른 점!

내부적으로는, 처음에 s1만 만들었을 때에는 값이 Heap에 있고, stack에 있는 pointer가 이 s1을 가리킬텐데

s2로 move되면, s1에 접근이 불가하게 하고, s2는 s1의 ptr와 같은 ptr을 갖는다.

 

19.5 Clone

우리가 전통적으로 복사할 땐 얕은 복사와 깊은 복사를 배운다.

얕은 복사는 주소만 넘겨주고, 깊은 복사는 내부에 있는 값까지 Clone해서 넘겨주는 것.

(=Heap에 대한 reference를 복사하느냐, 아니면 Heap 메모리를 새로운 영역에 그대로 복사한 후 reference를 가져오느냐)

 

얕은 복사에선 한 쪽에서 객체를 해제해버리면, 다른 얕은 복사를 한 쪽에서 이 주소로 가리키는 것이 사라지니

댕글링 포인터(Dangling Pointer)의 문제가 생긴다.

댕글링 포인터란?
이미 해제된 메모리 영역을 가리키고 있는 포인터로, 유효하지 않은 메모리를 가리키니 Segmantation fault가 발생.

 

Rust에선 그래서 Copy와 Clone을 나눠놓았다.

 

  • Copy = 얕은 복사 : 기본 타입(i32, i64, f32, 64처럼 복사해도 아무 영향x)을 제외하면 모두 구현되어 있지 않다. String, Vector처럼 동적할당 하는 애들은 이런 Copy가 구현 x.
  • Clone = 깊은 복사!

위의 name.clone() 처럼 내가 소유권을 넘기고 싶지 않으면, 복사본을 하나 만들어 넘기면

복사본의 소유권을 넘기는거라 원본도 또 쓸 수 있다. 이렇게 쓰는게 Rust에선 매우 일반적.

19.6 Copy Types

위처럼 i32는 Copy를 이미 갖고 있고, Point라는 구조체도 그것의 멤버들이 모두 Copy Trait이 구현되어 있으므로

assign하더라도 얕은 복사가 일어나 전혀 문제되지 않는다!

19.7 The Drop Trait

어떤 값이 Scope 밖으로 빠져나가면, Drop이라는 Trait이 호출된다.

위와 같은 예제에서 출력이 어떤 순으로 될까?

참고로 Scope를 벗어나면 위의 drop 함수가 호출되어 출력된다.

 

답은

더보기

Exiting block B

Dropping d

Dropping c

Exiting block A

Dropping b

Dropping a

Exiting main

*참고로 모든 언어에서 해제는 선언 수순의 반대이다.

 

마지막에 c, d 순으로 선언되었으니 d, c 순으로 해제됨.
그리고 b가 해제되고, 뒤에는 내가 a를 명시적으로 drop을 호출했으니 마지막으로 해제됨.

반응형
Comments