LoginSignup
22
7

More than 5 years have passed since last update.

Rustでゼロサイズのヒープ領域を確保した時の挙動

Posted at

BoxVecは与えられた型に応じてヒープ領域を確保してくれますが、確保するメモリサイズが0であるときの詳しい挙動を調べてみました。

Box<T>Tがゼロサイズの場合

Rustでは、次のような型のサイズは0になります。

()
[u8; 0]
struct Foo;
enum Bar;

これらの型の値をBoxで生成したらどうなるのでしょうか。例として、ユニット型()Boxで確保した場合、そのポインタはどこを指しているのか調べてみます。

let b = Box::new(());
let p = &*b as *const ();
println!("{:p}", p);

結果は次のようになります。

0x1

Boxを使えば、ヒープ領域を確保してくれるはずですが、この場合は明らかにポインタがヒープ領域を指していません。どうやら、ゼロサイズの型については、Boxはダングリングポインタになるようです。といっても、ゼロサイズの場合はポインタの先を参照することは無いので、未定義動作にはなりません。

Tがゼロサイズの時、Box<T>がダングリングポインタにになることで、ジェネリックなコードが書きやすくなっています。なぜなら、プログラマがTのサイズを気にすることなく、Box側がヒープ確保のオーバーヘッドが最小になるように面倒をみてくれるためです。

長さ0のVec

Vec::new()vec![]により、長さ(要素の数)が0のVecを作ることができます。このとき作ったVecはどこを指しているのか調べてみます。

let v: Vec<i32> = vec![];
let p = v.as_ptr();
println!("{:p}", p);

let v: Vec<i32> = Vec::new();
let p = v.as_ptr();
println!("{:p}", p);

実行結果は次のようになります。

0x4
0x4

Vecについてもダングリングポインタになり、ヒープ領域の確保は行われていません。これでヒープ領域割り当てのコストを心配すること無しに長さ0のVecを作ることができます 1。長さが0でもヒープ領域を確保したい場合は、Vec::with_capacity()を使いましょう。また、Tそのものがゼロサイズであれば、Vec<T>はいかなる場合でもヒープ領域を確保しません。

ダングリングポインタについて

上の例に示したBoxVecが、なぜnullではなく0x1や0x4というアドレスを指すダングリングポインタになっているのでしょうか。これは、Boxは絶対に0(null)にならないことを保証しているためです。これにより、Option<Box<T>>Box<T>と同じサイズとなるわけです。Vecの場合は、内部では絶対にnullにならないポインタ型Unique<T> 2 を使っているため、こちらもヒープ領域を確保しない場合はダングリングポインタが格納されます。

ちなみに、Rustには公式にダングリングポインタを作る方法が用意されています。NonNull<T>という型は、絶対にnullにならないような制約をつけたポインタ型ですが、これにはdangling()というメソッドが存在し、次のように実装されています(一部省略)。

impl<T: Sized> NonNull<T> {
    pub fn dangling() -> Self {
        unsafe {
            let ptr = mem::align_of::<T>() as *mut T;
            NonNull::new_unchecked(ptr)
        }
    }
}

この実装では、生成されたダングリングポインタのアドレスはその型のアラインメントのサイズと同じです。空のVec<i32>の指している先が0x4なのも同様に、i32のアライメントが4のためだったことが分かります。たとえダングリングポインタでも、ポインタのアラインメントの規則は守られているわけですね。

ダングリングポインタの作り方や、BoxVecの内部表現は、普通にRustを書く上で気にする必要はありません。しかし、もしヒープ領域の確保・解放が関わるunsafeなコードを書く場合は、これらの知識が必要になるでしょう。


  1. ただし、一度ヒープ領域を確保したVecは、要素を全部破棄してlen() == 0になっても、そのまま使ったヒープ領域を確保し続けます。このヒープ領域を解放させるには、Vec自体をDropするか、shrink_to_fit()メソッドを呼ぶ必要があります。 

  2. Rust1.26時点でUniqueはunstableなので、将来変更されるかもしれません。Uniqueのダングリングポインタの生成方法はUnique::empty()であり、その中身はNonNull::dangling()と同じです。 

22
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
7