メモリアライメントについて気になったので調べた。
実際に確認してみた
main.rs
use std::mem::size_of_val;
#[warn(dead_code)]
#[repr(C)]
struct Foo {
a: u8,
b: u32,
c: u16,
d: [u8; 3],
}
impl Default for Foo {
fn default() -> Self {
Self {
a: 0,
b: 0,
c: 0,
d: [0, 0, 0],
}
}
}
fn main() {
let foo = Foo::default();
let ptr: *const Foo = &foo;
let a_ptr: *const u8 = &foo.a;
let b_ptr: *const u32 = &foo.b;
let c_ptr: *const u16 = &foo.c;
let d_ptr: *const u8 = foo.d.as_ptr();
let base = ptr as usize;
println!(
"foo\taddr:\t{}\tsize:\t{}",
ptr as usize - base,
size_of_val(&foo)
);
println!(
"foo.a\taddr:\t{}\tsize:\t{}",
a_ptr as usize - base,
size_of_val(&foo.a)
);
println!(
"foo.b\taddr:\t{}\tsize:\t{}",
b_ptr as usize - base,
size_of_val(&foo.b)
);
println!(
"foo.c\taddr:\t{}\tsize:\t{}",
c_ptr as usize - base,
size_of_val(&foo.c)
);
println!(
"foo.d\taddr:\t{}\tsize:\t{}",
d_ptr as usize - base,
size_of_val(&foo.d)
);
}
$ cargo run
foo addr: 0, size: 16
foo.a addr: 0, size: 1
foo.b addr: 4, size: 4
foo.c addr: 8, size: 2
foo.d addr: 10, size: 3
確かにpaddingが入っていることがわかった。
foo.a | (padding) | (padding) | (padding) |
foo.b | foo.b | foo.b | foo.b |
foo.c | foo.c | foo.d | foo.d |
foo.d | (padding) | (padding) | (padding) |
なお、Rustのデフォルトでは構造体のレイアウトを変えて最適化が走り、上記のようにはならない様。(repr(C)
を外すとわかる)
Rustの構造体メモリレイアウト - ryochack.blog
なぜ必要なのか
調べていると「CPUがメモリにアクセスしやすくする」「メモリにアクセスする回数が増える」的なことが多く見られたがあまりイメージがつかないでいた。
2.アラインメントは何か? | データ型のアラインメントとは何か,なぜ必要なのか? を見て雰囲気わかった気になった。
CPUとメモリ間にはデータをやりとりするためのデータバスというものがあり、一度にアクセスできるバイト数が決まっているらしい。
そのサイズにアライメントされていない場合、複数回のメモリアクセスが必要になったり、SIGBUSが発生してコアダンプする様。(CPUによって異なる様)