Rustでは、コンパイラはアライメントと構造体やenumのレイアウトに関する知識を利用して、メモリ内でデータを効率的に配置しようとします。この問題は、データ型のサイズが大きすぎるとメモリの過剰消費につながる低レベルおよびシステム開発において特に重要です。
構造体の自動アライメントは多くの言語の特徴ですが、Rustのコンパイラはレイアウトに関して厳格な保証を提供し(最適化を許可する)、enumの場合はすべてのバリエーションのメモリを統合してコンパクトに保存します。
構造体やenumにおけるフィールドの順序と型は、アライメントの特性により最終的な型のサイズに影響を与えます。不適切な順序は「パディング」を増やします — 使用されていないバイト。関連データを持つenumでは、最大のバリエーションがサイズを決定し、一部の構造はenumを予想外に大きくする可能性があります。
フィールドの順序を正しく指定し、型を選択し、std::mem::size_ofを通じてデータのサイズを分析します。enumの場合は、入れ子の構造体やポインタに注意が必要です。
コードの例:
struct Bad { a: u8, // 1バイト + 7バイトのパディング b: u64, // 8バイト } struct Good { b: u64, // 8バイト、最初からアライメント a: u8, // 1バイト + 7バイトのパディングが最後に }
サイズのチェック:
use std::mem::size_of; println!("{}", size_of::<Bad>()); // 16バイト println!("{}", size_of::<Good>()); // 16バイト(しかし、今は末尾からのパディング)
enumのためには:
enum Example { Unit, Num(u32), Pair(u64, u8), } println!("{}", size_of::<Example>()); // サイズは最大(オプションのサイズ)+ discriminant
重要な特徴:
構造体内のu8とu64のフィールドを入れ替えると、構造体のサイズは変わりますか?
いいえ、全体のサイズは最大のフィールドのアライメントに対して整然としているが、パディングはシフトします。これは、構造体が別の構造体に含まれている場合やFFIに渡される場合に重要です。
小さなenumが大きなメモリサイズを持つことはありますか?
はい、少なくとも1つのバリエーションが大きなオブジェクトまたは参照を含む場合、全体のenumサイズは最も「重い」バリエーションのサイズにdiscriminantを加えたものになります。
構造体のレイアウトは常にすべてのプラットフォームで同じですか?
いいえ、レイアウトとアライメントはアーキテクチャによって異なる場合があります。厳密な制御にはrepr(C)属性を使用します。
#[repr(C)] struct MyFFIStruct { x: u32, y: u8, }
大規模なコレクションでは埋め込まれたVecを持つenumが使用されますが、これはまれにしか出現せず、enumのサイズを10倍に増加させます。メモリが無駄に消費されます。
利点:
欠点:
enumは、いくつかの小さいenumに分割され、配列/コレクションは別々に維持され、希少なバリエーションにはBoxを使用し、レイアウトは#[repr(C)]を介して制御されます。サイズはsize_ofで検証されます。
利点:
欠点: