面试题答案
一键面试内存分配
- 数组:
- 数组的大小是固定的,在编译时就确定。其内存分配在栈上(除非是静态数组,静态数组分配在静态存储区)。例如,
let arr: [i32; 5] = [1, 2, 3, 4, 5];
这里的arr
占用的内存空间在栈上,且空间大小为5 * sizeof(i32)
。
- 数组的大小是固定的,在编译时就确定。其内存分配在栈上(除非是静态数组,静态数组分配在静态存储区)。例如,
- 切片:
- 切片是动态大小的,其本身是一个胖指针(在64位系统上,大小为16字节,包含一个指向数据起始位置的指针和一个表示切片长度的整数)。切片所指向的数据通常分配在堆上。例如,
let s = "hello".as_bytes();
这里s
本身在栈上,但指向的数据在堆上。
- 切片是动态大小的,其本身是一个胖指针(在64位系统上,大小为16字节,包含一个指向数据起始位置的指针和一个表示切片长度的整数)。切片所指向的数据通常分配在堆上。例如,
所有权
- 数组:
- 数组拥有其包含的数据的所有权。当数组离开作用域时,数组中的数据也会被销毁。例如:
{
let arr = [1, 2, 3];
} // 这里arr离开作用域,数组及其数据被销毁
- 切片:
- 切片并不拥有它所指向的数据的所有权,它只是借用数据。切片所指向的数据的所有权由其他实体(如拥有堆上数据的
Vec
)持有。例如:
- 切片并不拥有它所指向的数据的所有权,它只是借用数据。切片所指向的数据的所有权由其他实体(如拥有堆上数据的
let vec = vec![1, 2, 3];
let slice = &vec[..];
这里 vec
拥有数据所有权,slice
只是借用 vec
中的数据。
生命周期管理
- 数组:
- 数组的生命周期与其定义的作用域相同。一旦离开作用域,数组及其包含的数据都会被释放。例如:
fn test() {
let arr = [1, 2, 3];
// arr 的生命周期从这里开始
//...
}
// arr 的生命周期在这里结束,数据被释放
- 切片:
- 切片的生命周期依赖于它所借用的数据的生命周期。切片的生命周期必须在其所借用数据的生命周期之内。例如:
fn get_slice() -> &[i32] {
let arr = [1, 2, 3];
&arr // 错误!arr 在函数结束时被销毁,返回的切片指向已释放的内存
}
这里编译会报错,因为返回的切片指向的 arr
在函数结束时被销毁,导致悬垂指针。正确的做法是传递切片的所有权:
fn get_slice() -> Vec<i32> {
let arr = vec![1, 2, 3];
arr
}
导致编译错误或运行时问题的场景
- 编译错误:
- 如上述
get_slice
函数示例,当切片的生命周期超过其所借用数据的生命周期时,会导致编译错误。这是因为 Rust 编译器通过生命周期检查来确保内存安全,防止悬垂指针。
- 如上述
- 运行时问题:
- 虽然 Rust 本身通过所有权和生命周期系统避免了大部分运行时内存问题,但如果在涉及切片的操作中不小心,仍然可能出现问题。例如,使用不安全代码手动管理切片指针时,如果指针算术运算错误,可能导致访问越界,引发未定义行为。但只要遵循 Rust 的安全规则,这类运行时问题是可以避免的。