赞
踩
阅读《Programming Rust 2nd Edition》的 unsafe 章节时看到了这样一个黑科技,感觉很有意思
mod ref_with_flag { use std::marker::PhantomData; use std::mem::align_of; /// A `&T` and a `bool`, wrapped up in a single word. /// The type `T` must require at least two-byte alignment. /// /// If you're the kind of programmer who's never met a pointer whose /// 2⁰-bit you didn't want to steal, well, now you can do it safely! /// ("But it's not nearly as exciting this way...") pub struct RefWithFlag<'a, T> { ptr_and_bit: usize, behaves_like: PhantomData<&'a T> // occupies no space } impl<'a, T: 'a> RefWithFlag<'a, T> { pub fn new(ptr: &'a T, flag: bool) -> RefWithFlag<T> { assert!(align_of::<T>() % 2 == 0); RefWithFlag { ptr_and_bit: ptr as *const T as usize | flag as usize, behaves_like: PhantomData } } pub fn get_ref(&self) -> &'a T { unsafe { let ptr = (self.ptr_and_bit & !1) as *const T; &*ptr } } pub fn get_flag(&self) -> bool { self.ptr_and_bit & 1 != 0 } } }
这段代码创建了一个类似union
的类型RefWithFlag
,可以同时存放一个变量的地址和一个布尔值,比如这样使用
use ref_with_flag::RefWithFlag;
let vec = vec![10, 20, 30];
let flagged = RefWithFlag::new(&vec, true);
assert_eq!(flagged.get_ref()[1], 20);
assert_eq!(flagged.get_flag(), true);
变量flagged
同时存放了vec
的地址和true
,那么是怎么实现的呢?其实RefWithFlag
本质上是一个结构体,包含两个字段
pub struct RefWithFlag<'a, T> {
ptr_and_bit: usize,
behaves_like: PhantomData<&'a T> // occupies no space
}
ptr_and_bit
就是用来存放地址和布尔值的字段,在new()
时首先进行下面的检查
assert!(align_of::<T>() % 2 == 0);
如果类型T
的最小对齐字节不是偶数就会报错,为什么要求类型T
的最小对齐字节是偶数呢?这里涉及到一些内存对齐的概念,假设当前内存已经使用到地址x
,如果x
是偶数个字节,也就意味着地址x
最低位一定是 0(二进制中 2 的倍数最低位为 0),而如果x
是奇数个字节,而类型T
的最小对齐字节是偶数,那么在分配时也会从偶数地址开始分配,比如x+1
的位置,这样最低位仍然是 0。既然可以保证地址的最低位永远为 0,那就可以把布尔值存入到最低位,下面就是通过位运算实现了这一操作
ptr_and_bit: ptr as *const T as usize | flag as usize
虽然这一操作看起来像是 unsafe 的,但实际上只要不访问或者修改指向的内存就是安全的,然后在取值时再进行相应的还原即可
// 获取地址
let ptr = (self.ptr_and_bit & !1) as *const T;
// 获取布尔值
self.ptr_and_bit & 1 != 0
另外一个字段behaves_like
也很有意思,它不占用任何内存,看起来后面也没有用到,但却不能删除,因为它是用来接收生命周期和泛型参数,如果删掉 rust 就会警告
parameter `'a` is never used
parameter `T` is never used
在官方文档的介绍中说PhantomData<T>
类型的作用是“看起来像是拥有了类型 T”,其实就是可以为编译器提供更多信息,避免上面的警告。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。