Rust enum 메모리

mohadang·2023년 1월 27일
0

Rust

목록 보기
18/30
post-thumbnail

enum의 데이터

enum의 원소들은 C의 union처럼 동작할 수 있도록 한 개 이상의 자료형을 가질 수 있다.
enum이 match를 통해 패턴 일치될 때, 각각의 데이터 값에 변수명을 붙일 수 있다.

enum의 메모리 상세

  • enum 데이터 값은 가장 큰 원소의 메모리 크기와 같은 메모리 크기를 가진다. 이는 가능한 모든 값이 동일한 메모리 공간에 들어갈 수 있게 해준다.
  • 원소의 자료형(있는 경우)에 더하여, 각 원소는 무슨 태그에 해당하는지 나타내는 숫자값도 갖는다.

Ex)

#![allow(dead_code)] // 이 줄은 컴파일러 경고를 방지해줌

enum Species {
    Crab,
    Octopus,
    Fish,
    Clam,
}
enum PoisonType {
    Acidic,
    Painful,
    Lethal,
}
enum Size {
    Big,
    Small,
}
enum Weapon {
    Claw(i32, Size),
    Poison(PoisonType),
    None,
}

struct SeaCreature {
    species: Species,
    name: String,
    arms: i32,
    legs: i32,
    weapon: Weapon,
}

fn main() {
    // SeaCreature의 데이터는 stack에 있음
    let ferris = SeaCreature {
        // String struct도 stack에 있지만,
        // heap에 있는 데이터에 대한 참조를 갖고 있음
        species: Species::Crab,
        name: String::from("Ferris"),
        arms: 2,
        legs: 4,
        weapon: Weapon::Claw(2, Size::Small),
    };

    match ferris.species {
        Species::Crab => match ferris.weapon {
            Weapon::Claw(num_claws, size) => {
                let size_description = match size {
                    Size::Big => "큰",
                    Size::Small => "작은",
                };
                println!(
                    "ferris는 {}개의 {} 집게를 가진 게이다",
                    num_claws, size_description
                )
            }
            _ => println!("ferris는 다른 무기를 가진 게이다"),
        },
        _ => println!("ferris는 다른 동물이다"),
    }
}

Option의 크기

Option은 컴파일 타임에 크기를 결정해야 한다.
컴파일러는 Option의 크기는 T의 타입에 따라 가변적오르 메모리 할당이 발생한다.

use std::mem::size_of;
use std::mem::size_of_val;

// enum Option<T> {
//     Some(T),
//     None,
// }

struct Node32 {
    data1: i32,
}
struct Node64 {
    data1: i32,
    data2: i32,
}
struct NodeOpt32 {
    data1: Option<i32>,
}
struct NodeOpt64 {
    data1: Option<i32>,
    data2: Option<i32>,
}

fn main() {
    println!("{}", size_of::<Node32>());//4
    println!("{}", size_of::<Node64>());//8
    println!("{}", size_of::<NodeOpt32>());//8
    println!("{}", size_of::<NodeOpt64>());//16

    let a = Node32 {
        data1: 0
    };
    println!("{}", size_of_val(&a));//4

    let b = Some(1_i32);
    println!("{}", size_of_val(&b));//8

    let c = Some(1_i64);
    println!("{}", size_of_val(&c));//16

    let d = Some(String::from("aaaaaaaaaaaaaaa"));
    println!("{}", size_of_val(&d));//24
}

제네릭을 사용한다면 rust 컴파일러가 컴파일 타임에 메모리 크기를 결정 할 수 있도록 주의해야 한다.

use std::mem::size_of;
use std::mem::size_of_val;

struct Node<T> {
    data1: T,
}

fn main() {
    // 컴파일 타임에 결정 가능하여 문제 없음

    println!("{}", size_of::<Node<i32>>());//4
    println!("{}", size_of::<Node<i64>>());//8

    let a = Node::<i32> {
        data1: 0
    };
    println!("{}", size_of_val(&a));//4

    let b = Node::<i64> {
        data1: 0
    };
    println!("{}", size_of_val(&b));//8
}

재귀적인 타입 정의 에러는 C++ 뿐만 아니라 Rust에서도 문제 발생 할 수 있다.

// error 발생, Node<T> 타입 구조 결정하기 위해서 
// Node<T> 안에 data2("Node<T>") 멤버 타입 크기를 결정해야 한다.
struct Node<T> {
    data1: T,
    data2: Node<T>, //error
}

fn main() {
    println!("{}", size_of::<Node<i32>>());
    println!("{}", size_of::<Node<i64>>());
}

9  | struct Node<T> {
   | ^^^^^^^^^^^^^^ recursive type has infinite size
10 |     data1: T,
11 |     data2: Node<T>,
   |            ------- recursive without indirection

Option은 해결책이 되지 못한다. Option 역시 컴파일 타임에 크기를 결정한다.

struct Node<T> {
    data1: T,
    data2: Option<Node<T>>,//error
}

문제 해결을 위해서는 Box를 사용하여 Heap 메모리를 참조하도록 변경해야 한다.

use std::mem::size_of;
use std::mem::size_of_val;

struct Node<T> {
    data1: T,
    data2: Option<Box<Node<T>>>,
}
fn main() {
    println!("{}", size_of::<Node<i32>>());//16
    println!("{}", size_of::<Node<i64>>());//16
}

Option의 메모리 할당

Option은 stack 메모리에 위치

pub fn add(num: i128) -> Option<i128> {
    let a = Some(1948 + num);
    a
}

example::add:
        sub     rsp, 40           <-- 스택 메모리 할당
        mov     rcx, rsi
        mov     rdx, rdi
        add     rdx, 1948
        mov     qword ptr [rsp], rdx
        adc     rcx, 0
        seto    al
        mov     qword ptr [rsp + 8], rcx
        test    al, 1
        jne     .LBB0_2
        mov     rax, qword ptr [rsp + 8]
        mov     rcx, qword ptr [rsp]
        mov     qword ptr [rsp + 24], rcx
        mov     qword ptr [rsp + 32], rax
        mov     qword ptr [rsp + 16], 1
        mov     rax, qword ptr [rsp + 16]
        mov     rdx, qword ptr [rsp + 24]
        mov     rcx, qword ptr [rsp + 32]
        add     rsp, 40
        ret
.LBB0_2:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_1]
        mov     rax, qword ptr [rip + core::panicking::panic@GOTPCREL]
        mov     esi, 28
        call    rax
        ud2

Box처럼 힙 메모리 할당은 보이지 않음

pub fn add(num: i128) -> Box<i128> {
    let a = Box::new(1948 + num);
    a
}

example::add:
        sub     rsp, 40
        mov     rcx, rsi
        mov     rdx, rdi
        add     rdx, 1948
        mov     qword ptr [rsp + 8], rdx
        adc     rcx, 0
        seto    al
        mov     qword ptr [rsp + 16], rcx
        test    al, 1
        jne     .LBB5_4
        mov     edi, 16
        mov     esi, 8
        call    alloc::alloc::exchange_malloc   <--- 힙 메모리 할당
        mov     qword ptr [rsp], rax
        jmp     .LBB5_3
        mov     rcx, rax
        mov     eax, edx
        mov     qword ptr [rsp + 24], rcx
        mov     dword ptr [rsp + 32], eax
        mov     rdi, qword ptr [rsp + 24]
        call    _Unwind_Resume@PLT
        ud2
.LBB5_3:
        mov     rax, qword ptr [rsp]
        mov     rcx, qword ptr [rsp + 16]
        mov     rdx, qword ptr [rsp + 8]
        mov     qword ptr [rax], rdx
        mov     qword ptr [rax + 8], rcx
        jmp     .LBB5_5

Result

Result 역시 Option과 같은 enum이기에 Option과 같은 제약을 가진다.

profile
mohadang

0개의 댓글