赞
踩
Rust提供了两种裸指针供我们使用,const T和mut T。我们可以通过mut T修
改所指向的数据,而const T不能。在unsafe代码块中它们俩可以互相转换。
裸指针相对于其他的指针,如Box,&,&mut来说,有以下区别:
·裸指针可以为空,而且编译器不保证裸指针一定指向一个合法的内存地址;
·不会执行任何自动化清理工作,比如自动释放内存等;
·裸指针赋值操作执行的是简单的内存浅复制,并且不存在borrow checker的限
制。
创建裸指针是完全安全的行为,只有对裸指针执行“解引用”才是不安全的行
为,必须在unsafe语句块中完成。
示例代码如下:
fn main() {
let x = 1_i32;
let mut y : u32 = 1;
let raw_mut = &mut y as *mut u32 as *mut i32 as *mut i64; // 这是安全的
unsafe {
*raw_mut = -1; // 这是不安全的,必须在 unsafe 块中才能通过编译
}
println!(“{:X} {:X}”, x, y);
}
我们可以把裸指针通过as运算符执行类型转换。转换类型之后,它就可以把它
所指向的数据当成另外一个类型来操作了。原本变量y的类型是u32,但是我们对它
取地址后,最后将指针类型转换成了i64。此时,我们对该指针所指向的地址进行修
改会发生“类型安全”问题以及“内存安全”问题。编译,执行,这段代码的执行结果
为:
FFFFFFFF FFFFFFFF
可见,x原本是一个在栈上存在的不可变绑定,在我们通过裸指针对y做了修改
之后,x的值也发生了变化。原因就是,我们对指向y的指针类型做了转换,让它以
为自己指向的是i64类型,恰巧x就在y旁边,城门失火,殃及池鱼,x就被顺带一起
修改了。从这个示例我们可以看到,unsafe代码中可以做很多危险的事情。上面这
个例子就是一个错误的unsafe用法。
再比如:
fn raw_to_ref<'a>(p: *const i32) -> &'a i32 {
unsafe {
&*p
}
}
fn main() {
let p : &i32 = raw_to_ref(std::ptr::null::());
println!(“{}”, p);
}
编译,执行,可以看到发生了core dump。为什么呢?因为unsafe代码写错了。
这段代码里面直接用unsafe功能把一个裸指针转换为了一个共享引用,忽略了Rust
里面共享引用必须遵循的规则。在Rust中,&型引用、&mut型引用以及Box指针,
全部要求是合法的非空指针。在unsafe代码中,我们必须自己从逻辑上保证这一
点,否则就是不可容忍的严重bug。(注意在safe代码中是没办法构造出这样的场景
的。)有些初学者可能会在写FFI,封装C代码的时候犯这样的错误。改正方法如
下:
fn raw_to_ref<'a>(p: *const i32) -> Option<&'a i32> {
if p.is_null() {
None
} else {
unsafe { Some(&*p) }
}
}
fn main() {
let p : Option<&i32> = raw_to_ref(std::ptr::null::());
println!(“{:?}”, p);
}
Rust的各种指针还有一些重要约束,比如&mut型指针最多只能同时存在一个。
这些约束条件,在unsafe场景下是很容易被打破的,而编译器并没有能力帮我们自
动检查出来。我们之所以需要unsafe,只是因为某些代码只有在特定条件下才是安
全的,而这个条件我们没有办法利用类型系统表达出来,所以这时候需要依靠我们
自己来保证。
大家千万不要到处滥用unsafe。当你不得不使用unsafe的时候,请一定注意,这
并不意味着你就可以乱写不安全的代码,相反,它的意思是“编译器请相信我,这
段代码依然是安全的,它的安全性由我自己负责”。
裸指针也有很多有用的成员方法,读者可以参考标准文档中的“Primitive Type
Pointer”。比如,裸指针并不直接支持算术运算,而是提供了一系列成员方法offset
wrapping_offset等来实现指针的偏移运算。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。