rustで動的にバッファを確保する方法
Box
を使うVec
を使うstd::heap::Alloc
を使う- placement-in を使う
malloc
を使う
1. Box
を使う
rustで動的にメモリを確保する方法といってまず思いつくのはBox
を使う方法だと思います.例えば,以下のようにすれば長さ1000のu8のバッファを確保できます.
let buffer : Box<[u8]> = Box::new([0;1000]);
ただし,この方法は以下のような特徴があります.
- 確保した領域は必ず初期化する必要がある.
- 一旦スタック上にデータを確保したあとに,ヒープにそのデータをコピーする
1.に関しては,これは変数は初期化しないと利用できないというrustの原則に則ったものですが,場合によってはこの初期化コストが大きい場合があります.また,2. の方が問題で,あまりにも大きい領域を確保しようとするとスタックオーバフローが発生する可能性があります.コピー分のオーバヘッドも生じます.
2. Vec
を使う
Vec
を使う場合はVec::new()
で可変長配列を作成した後Vec::push
でデータを追加していくのが最も基本的な方法ですが,当然効率はあまり良くありません.
Vec::with_capacity
を使うと,指定したサイズ分だけのメモリ領域を確保してくれます.さらに,Vec::set_len
を使うことで,初期化をスキップして確保した領域にアクセスすることができます.ただし,set_len
はunsafeです.
unsafe{ let len = 1000; let mut vec = Vec::<u8>::with_capacity(len); vec.set_len(len); }
また,確保した要素にアクセスする際rustはデフォルトで境界のチェックをしますが,
そのオーバヘッドを減らしたい場合はget_unchecked
あるいはget_unchecked_mut
が使えます((get_unchecked
やget_uchecked_mut
はsliceの関数で,boxやvecからderef coercionを通じて呼び出せます)).
ffiで利用するなどでraw pointerが欲しい場合は以下のような手法が使えます.
unsafe fn alloc(len: usize) -> *mut u8 { let mut vec = Vec::<u8>::with_capacity(len); vec.set_len(len); Box::into_raw(vec.into_boxed_slice()) as *mut u8 } unsafe fn free(raw: *mut u8, len : usize) { let s = std::slice::from_raw_parts_mut(raw, len); let _ = Box::from_raw(s); }
Vec::with_capacity
で指定した長さ分の領域を確保したあと,それをvec::into_boxed_slice
でBoxに変換し,さらにそれをBox::into_raw
することで実際のポインタを得ます.
確保した領域を解放する場合はポインタを再びslice::from_raw_parts_mut
でスライスに直し,それをさらにBox::from_raw
でBoxに直してBoxのDropが呼ばれるようにします.
上記のコードはservoの一部のコードを参考にしたものです. 実際のservoのコードではアラインメントをワード境界に揃えるため,以下のようなことをしています (簡単化のため実際のコードとちょっと異なります.ここでは実際に確保したバイト数も戻り値として返しています).
unsafe fn alloc_buffer(len : usize) -> (*mut u8, usize){ let word_size = std::mem::size_of::<usize>(); let num = (len + word_size-1) / word_size; let mut vec = Vec::<usize>::with_capacity(num); vec.set_len(num); (Box::into_raw(vec.into_boxed_slice()) as *mut u8, num*word_size) }
3. std::heap::Alloc
を使う
rustのメモリアロケータはAllocator traitを実装することになっており,またstd::heap::Heap
でデフォルトのアロケータにアクセスできます.コードはliballoc
にあります.
これを利用すると,以下のようにメモリを確保することができます.
#![feature(allocator_api)] use std::heap::{Alloc, Heap, Layout}; // 第一引数: サイズ, 第二引数: アラインメントサイズ let layout = Layout::from_size_align(10, std::mem::align_of::<u32>()).unwrap(); unsafe { let p = Heap.alloc(layout.clone()).unwrap() as *mut u8; // ... Heap.dealloc(p, layout); }
この関数を利用すれば,アラインメントも指定可能です.raw pionterが欲しい場合,この手法が一番良さそうに見えますが,残念ながらこの機能はunstableなため,nightlyな環境でしかまだ使えません.また,今後インタフェースが変わる可能性も十分あると思います.(詳細: https://github.com/rust-lang/rust/issues/32838)
4. placement-in を使う
こちらも現時点ではunstableな機能ですが,確保したメモリ上に直接データを配置するplacement-in構文があります(詳細: https://github.com/rust-lang/rust/issues/27779). これを利用すると,boxでも大きなメモリ領域を確保することが可能です.
#![feature(placement_in_syntax, box_heap)] // OK let x: Box<[u8]> = std::boxed::HEAP <- [0;10000000]; // これは自分の環境ではoverflow // let b: Box<[u8]> = Box::new([0; 10000000]);
5. malloc
を使う
場合によってはlibc crateを使ってmalloc
& free
を直接呼んでしまうのも手かもしれません.こちらも当然unsafeです.
// https://github.com/rust-lang/libc unsafe { let p : *mut c_void = libc::malloc(10); // ... libc::free(p) }