int 的 align_of
align_of 是根据读取变量的效率进行计算:
memory 0 1 2 3 4 5 6 7 8 9 10 11
int good
baaaaaaad
int 类型占 32bit, 即 4 个字节, 假设 CPU 从 0 位置开始读取, 一次读取 4 个字节正好满足, 所以 int 放在 0123 的位置比放在 3456 的位置更有利于被 CPU 读取. 简而言之, 原始类型的 align_of 就是其 size_of,
下面是 Rust 中原始类型的对齐和大小, 由于 64 位系统的数据总线宽度是 64bit, 导致 i128 和 u128 的对齐和大小不一致.
bool align: 1, size: 1
char align: 4, size: 4
f32 align: 4, size: 4
f64 align: 8, size: 8
i128 align: 8, size: 16
i16 align: 2, size: 2
i32 align: 4, size: 4
i64 align: 8, size: 8
i8 align: 1, size: 1
isize align: 8, size: 8
u128 align: 8, size: 16
u16 align: 2, size: 2
u32 align: 4, size: 4
u64 align: 8, size: 8
u8 align: 1, size: 1
usize align: 8, size: 8
结构体的 align_of
一个结构 P, 有三个成员, 如下所示: (假设结构体中成员的声明顺序和在内存中的顺序保持一致)
// in src/main.rs
fn main() {
use std::mem;
#[repr(C)] // 保证成员在内存中的顺序和声明的顺序保持一致 (否则可能被编译器优化)
struct P {
i: i32, // size_of = 4
s: i8, // size_of = 1
l: i64, // size_of = 64
}
println!("P align: {}", mem::align_of::<P>());
println!("P size: {}", mem::size_of::<P>());
}
上面的代码输出:
P align: 8
P size: 16
输出结果显示 P 的 align 和 size 是不一样的, 为什么会这样呢? 看下 P 在内存中的表示:
read |--- 1st ---| |--- 2nd ---|
memory 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
P i------ s l----------------------
P 的 align_of 的计算过程:
- i: 如果一次读取完毕, 需读取 4 个字节, 所以对齐到 4 可以很方便的读取 i.
- s: 如果一次读取完毕, 需读取 1 个字节, 所以对齐到 1 就可以, 但 i 需要对齐到 4, 所以对齐仍为 4.
- l: 如果一次读取完毕, 需读取 8 个字节, 所以对齐到 8 可以很方便的读取 l. 于是 P 的对齐就是 8.
P 的 align_of = max({size_of(f)|f∈P}) = max(1, 4, 8) = 8
P 的 size_of 的计算过程:
P 的 align_of = 8, 意味着读取 P 的时候, 每次读取 8 个字节.
- 第一次读取 8 个字节, 其中包含了 i:i32 和 s:i8
- 第二次读取 8 个字节, 包含了 l:i64
P 的 size_of = 对齐 * 读取次数 = 8 + 8 = 16
P 变化了
如果在上面的基础上, 增加一个 u8 类型的成员 s2:
fn main() {
use std::mem;
#[repr(C)]
struct P {
i: i32,
s: i8,
s2: i8,
l: i64,
}
println!("P align: {}", mem::align_of::<P>());
println!("P size: {}", mem::size_of::<P>());
}
上面的代码输出:
P align: 8
P size: 16
根据结果显示, 虽然增加了一个成员 s2, 但 P 的大小没有变, 仍然是 16, 为什么会这样? 看下 P 在内存中的结构:
read |--- 1st ---| |--- 2nd ---|
memory 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
P i------ s s2 l----------------------
根据 P 的内存结构计算如下:
align_of = max({sizeof(f)|f∈P}) = max(4, 1, 1, 8) = 8
size_of = 对齐 * 读取次数 = 8 * 2 = 16
原来是这样啊, 无论是否有 s2 这个成员, 5 6 7 这三个字节都被一次性读取了.
P 又变化了
在上面的例子的基础上, 如果把 s2 换个位置, 放在最后:
fn main() {
use std::mem;
#[repr(C)]
struct P {
i: i32,
s: i8,
l: i64,
s2: i8,
}
println!("P align: {}", mem::align_of::<P>());
println!("P size: {}", mem::size_of::<P>());
}
上面代码输出:
P align: 8
P size: 24
成员没有增删, 只是移动了位置, 但 P 的大小发生了变化, 怎么回事? 看下内存结构:
read |--- 1st ---| |--- 2nd ---| |--- 3rd ---|
memory 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
P i------ s l---------------------- s2
align_of = max({sizeof(f)|f∈P}) = max(4, 1, 8, 1) = 8
size_of = 对齐 * 读取次数 = 8 * 3 = 24
s2 虽然只占 1 个字节, 但由于 align = 8, 所以第三次一次性读取了 8 个字节.
练习题
fn main() {
use std::mem;
#[repr(C)]
struct P {
i: i32,
s: i8,
s2: i8,
s3: i8,
s4: i8,
s5: i8,
l: i64,
}
println!("P align: {}", mem::align_of::<P>());
println!("P size: {}", mem::size_of::<P>());
}
画一下 P 内存结构, 并计算 P 的大小.
read |--- 1st ---| |--- 2nd ---| |--- 3rd ---|
memory 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
P i------ s s2 s3 s4 s5 l-----------------------
align_of = max({sizeof(f)|f∈P}) = max(4, 1, 1, 1, 1, 1, 8) = 8
size_of = 对齐 * 读取次数 = 8 * 3 = 24