プログラム メモリの見直し

プログラムは、次の 2 つの方法でメモリを割り当てます。

  • スタック: ローカル変数用の連続したメモリ領域。

    • 値のサイズは固定されており、コンパイル時に判明しています。
    • 非常に高速: スタック ポインタを移動するだけです。
    • 関数呼び出しによって行われるため、管理が容易です。
    • メモリ局所性に優れています。
  • ヒープ: 関数呼び出しに依存しない値の保持領域。

    • 値のサイズは動的で、実行時に決定されます。
    • スタックよりやや低速で、何らかののブックキーピングが必要です。
    • メモリの局所性が保証されません。

String を作成すると、スタックには固定サイズのメタデータが配置され、ヒープにはサイズが動的に決定されるデータ(実際の文字列)が配置されます。

fn main() {
    let s1 = String::from("Hello");
}
StackHeaps1capacity5ptrHellolen5
This slide should take about 5 minutes.
  • StringVec により実現されているため、容量と長さがあり、可変であればヒープ上の再割り当てによって拡張できることを説明します。

  • 受講者から尋ねられた場合は、システム アロケータを使用してメモリ領域がヒープから割り当てられること、Allocator API を使用してカスタム アロケータを実装できることを説明してください。

その他

unsafe Rust を使用してメモリ レイアウトを調べることが出来ます。ただし、これは当然ながら安全でないことを指摘する必要があります。

fn main() {
    let mut s1 = String::from("Hello");
    s1.push(' ');
    s1.push_str("world");
    // 自宅では行わないでください。これは説明のみを目的としています。
    // String はそのレイアウトを保証しないため、未定義の動作が
    // 発生する可能性があります。
    unsafe {
        let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1);
        println!("capacity = {capacity}, ptr = {ptr:#x}, len = {len}");
    }
}