⚠️

【rust】【unsafe】メモリを直接操作してみる(生ポインタ)【取扱注意】

2023/10/14に公開

前回紹介したavltriee (変形AVL木)ですが、
内部的にはメモリを直接読み書きするという事をやっています。

rustはメモリ安全性を謳う言語ですので、このような事は普通はやりませんし推奨もしません。

が、例えばファイルから読み込みんだデータをそのまま構造体にマッピングしたい場合等どうしてもメモリを直接参照したい場合があります。
Cや他の言語で書かれた関数を呼び出すためのFFIを使う場合などメモリ関連の操作を避けられない場合もあります。

avltrieeはそもそも現在開発中のデータベースエンジンの一部をスピンオフして別クレート化したもので、もともとは
mmapしたメモリ領域を直接操作してファイルと同期し、データベースのインデックスとして使用するという目的がありました。

Vecで素直に実装した方が通常の用途には利便性が高かったとは思いますが、mmapと組み合わせずに単独で使う場合、予めメモリ領域を確保しなければならないというrustらしかぬ独特の使い勝手になってしまっているのはそのためです。

さて、本題に入ります。

Avltrieeの構造とコンストラクタはこのようなコードになっています。

pub struct Avltriee<T: Copy> {
	node_list: ManuallyDrop<Box<AvltrieeNode<T>>>,
}

pub fn new(node_list: *mut AvltrieeNode<T>) -> Avltriee<T> {
	Avltriee {
		node_list: ManuallyDrop::new(unsafe { Box::from_raw(node_list) }),
	}
}

new()の引数に
*mut AvltrieeNode<T>
を取っていますが、これは生ポインタを示す記法となります。

呼び出し元の方はこのような記述になります。

let mut list: Vec<AvltrieeNode<i64>> = (0..=10).map(|_| AvltrieeNode::new(0, 0, 0)).collect();
let rl = &mut list;
let mut t = Avltriee::new(rl.as_mut_ptr());

Vecに対して
.as_mut_ptr()
を呼び出すことでmutな生ポインタが得られます。

この生ポインタを受け取り、
unsafe { Box::from_raw(node_list) }

Box<AvltrieeNode<T>>
を作成します。
ただし、ここで注意しなければならないのが、この方法で生成したBoxはAvltrieeが破棄されるときに「普通の方法で」破棄されるという事です。

Avltrieeではメモリの確保と解放は呼び出し元が行うことが期待されます。ですのでAvltriee側でメモリを開放しては呼び出し元とAvltriee側で二重にメモリ開放処理が実行され、よくないことが起こります。

そこでManuallyDropで包むことでDropの方法をこちらで好きにできます。
今回の場合メモリの開放は呼び出し側で行われる事が期待されるためManuallyDropで包むだけで何もしません。

そして、このようにして生ポインタをゲットしたわけですが、このアドレスに対して実際に値の読み書きをするための仕組みがAvltrieeNode<T>を取得するための

unsafe fn offset<'a>(&self, offset: u32) -> &'a AvltrieeNode<T> {
	&*(self.node_list.as_ref() as *const AvltrieeNode<T>).offset(offset as isize)
}

unsafe fn offset_mut<'a>(&mut self, offset: u32) -> &'a mut AvltrieeNode<T> {
	&mut *(self.node_list.as_mut() as *mut AvltrieeNode<T>).offset(offset as isize)
}

です。

こちらは通常の参照を返します。
Avltrieeの構造体は生ポインタをBox<AvltrieeNode<T>>で装っているので、
一旦これを生ポインタにキャストし、offsetで指定の行のアドレスを取得し、AvltrieeNodeに変換して返しています。

この返り値は普通の参照として操作できます。書き込みをした場合は(当たり前ですが)メモリ上に値が上書きされます。

メモリを直接操作する場合は確保したアドレス空間の境界チェックは自力で行う必要があったり、unsafeな制御となりますが、逆にその辺りさえ十分に気を付ければ問題はありません。

このような生ポインタの直接操作は出来ればやらない方が良いとは思いますが、どうしてもやらざるを得ない場合の参考になれば幸いです。

Discussion