1. ホーム
  2. pointers

[解決済み] ファットポインターとは何ですか?

2022-05-15 10:21:13

質問

ファットポインタという言葉をいくつかの文脈で読みましたが、具体的にどういう意味で、Rustではどのようなときに使われるのかがわかりません。このポインタは通常のポインタの2倍の大きさがあるようですが、その理由はよくわかりません。また、traitオブジェクトと関係があるようです。

どのように解決するのか?

ファットポインター(fat pointer")という用語は、以下のような参照や生のポインターを指すのに使われます。 動的にサイズ調整された型 (DSTs) - スライスや特性オブジェクトへの参照や生ポインタを指します。 ファットポインターはポインターに加え、DSTを"complete"にするいくつかの情報(例えば長さ)を含んでいます。

Rustで最もよく使われる型は ではなく DSTではありませんが、コンパイル時に既知の固定サイズを持っています。これらの型は を実装しています。 Sized 特性 . 動的なサイズのヒープバッファを管理する型(例えば Vec<T> のように) は Sized の正確なバイト数をコンパイラが知っているからです。 Vec<T> のインスタンスがスタック上に占める正確なバイト数をコンパイラが知っているからです。現在、Rustには4種類のDSTがあります。



スライス( [T]str )

タイプ [T] (すべての T は動的にサイズ調整されます(特殊なquot;文字列スライスの str ). そのため、通常、これは &[T] または &mut [T] というように、参照の後ろについています。この参照はいわゆるファットポインタと呼ばれるものです。確認してみましょう。

dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());

これは(多少の後始末をしながら)印刷されます。

size_of::<&u32>()      = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>()    = 16

というように、通常の型への参照が u32 への参照は 8 バイトの大きさであり、配列 [u32; 2] . この2つの型はDSTではありません。しかし [u32] はDSTであるため、その参照は2倍となります。 スライスの場合、DST を "completes"する追加データは、単に長さだけです。 ということは &[u32] はこのようなものです。

struct SliceRef { 
    ptr: *const u32, 
    len: usize,
}



トライット・オブジェクト ( dyn Trait )

traitをtraitオブジェクトとして使用する場合(つまり、型消去、動的ディスパッチ)、これらのtraitオブジェクトはDSTとなります。例

trait Animal {
    fn speak(&self);
}

struct Cat;
impl Animal for Cat {
    fn speak(&self) {
        println!("meow");
    }
}

dbg!(size_of::<&Cat>());
dbg!(size_of::<&dyn Animal>());

これは(多少の後始末をしながら)印刷されます。

size_of::<&Cat>()        = 8
size_of::<&dyn Animal>() = 16

もう一度 &Cat が 8 バイトしかないのは Cat は通常の型だからです。しかし dyn Animal は形質オブジェクトであるため、動的にサイズが変更されます。そのため &dyn Animal は16バイトの大きさです。

traitオブジェクトの場合、DSTを完成させる追加データはvtableへのポインタ(vptr)です。 vtableとvptrの概念はここでは説明しきれませんが、この仮想ディスパッチコンテキストで正しいメソッド実装を呼び出すために使用されます。vtableは静的なデータで、基本的には各メソッドの関数ポインタのみが格納されています。これによって、traitオブジェクトへの参照は基本的に次のように表現されます。

struct TraitObjectRef {
    data_ptr: *const (),
    vptr: *const (),
}

(これは、抽象クラスのvptrがオブジェクト内に格納されるC++とは異なります。どちらのアプローチにも利点と欠点があります)。



カスタムDST

最後のフィールドが DST である構造体を持つことで、独自の DST を作成することは可能です。しかし、これはかなりまれなことです。顕著な例としては std::path::Path .

カスタムDSTへの参照やポインタもファットポインタとなります。追加されるデータは構造体内のDSTの種類に依存します。



例外です。外部型

RFC 1861 では extern type 機能が導入されました。Extern型もDSTですが、それらへのポインタは ではなく ファットポインタです。もっと正確に言うと、RFCが言っているように

Rustでは、DSTへのポインタは指されているオブジェクトに関するメタデータを持ちます。文字列やスライスの場合はバッファの長さ、trait オブジェクトの場合はオブジェクトの vtable です。extern 型の場合、メタデータは単に () . これは、外部型へのポインタのサイズが usize (つまり、ファットポインタではありません)。

しかし、もしあなたがCのインターフェースと対話しないのであれば、おそらくこれらのextern型を扱う必要はないでしょう。






上記では、不変参照に対するサイズを見てきました。ファットポインターは、ミュータブル参照、イミュータブル生ポインター、ミュータブル生ポインターのいずれでも同じように機能します。

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16