MST

星途 面试题库

面试题:Rust中栈内存与堆内存存储差异在变量生命周期上的体现

在Rust中,简述栈内存和堆内存存储的变量在生命周期方面有何不同?请举例说明如何通过所有权和借用规则来管理这种差异,比如在函数调用和结构体使用场景下。
48.6万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

栈内存和堆内存变量生命周期差异

  1. 栈内存
    • 栈上存储的变量生命周期通常较短,并且遵循后进先出(LIFO)的原则。当一个函数调用结束,该函数栈帧中的所有变量(在栈上分配)都会被自动释放。例如,基本数据类型(如i32bool等)默认存储在栈上。
    • 示例:
fn main() {
    let num: i32 = 10;
    // num存储在栈上,当main函数结束,num占用的栈空间会被释放
}
  1. 堆内存
    • 堆上存储的变量生命周期相对灵活,但管理更复杂。Rust使用所有权系统来管理堆内存变量的生命周期。当一个堆上变量的所有者超出其作用域,Rust会自动释放该变量占用的堆内存。例如,Box类型是用于在堆上分配内存的智能指针。
    • 示例:
fn main() {
    let boxed_num = Box::new(10);
    // boxed_num是一个指向堆上数据的智能指针,当main函数结束,
    // boxed_num超出作用域,堆上分配的内存会被释放
}

通过所有权和借用规则管理差异

函数调用场景

  1. 所有权转移
    • 当一个拥有堆内存数据所有权的变量作为参数传递给函数时,所有权会发生转移。函数成为新的所有者,调用结束后,函数栈帧销毁时会释放堆内存。
    • 示例:
fn take_ownership(vec: Vec<i32>) {
    // vec现在是Vec<i32>的所有者,当函数结束,vec占用的堆内存会被释放
}

fn main() {
    let my_vec = vec![1, 2, 3];
    take_ownership(my_vec);
    // 这里my_vec不再有效,因为所有权已转移给take_ownership函数
}
  1. 借用
    • 如果不想转移所有权,可以使用借用。借用允许函数临时访问变量,而不获取所有权。有两种类型的借用:不可变借用(&)和可变借用(&mut)。
    • 示例:
fn print_vec(vec: &Vec<i32>) {
    for num in vec {
        println!("{}", num);
    }
    // vec是借用的,函数结束后,vec所引用的数据所有权仍在调用者手中
}

fn main() {
    let my_vec = vec![1, 2, 3];
    print_vec(&my_vec);
    // my_vec仍然有效,因为所有权未转移
}

结构体使用场景

  1. 结构体中包含堆内存数据
    • 当结构体的字段包含堆内存数据(如BoxVec)时,结构体实例的生命周期管理遵循所有权规则。当结构体实例超出作用域,其所有字段(包括堆内存数据)都会被释放。
    • 示例:
struct MyStruct {
    data: Vec<i32>
}

fn main() {
    let s = MyStruct { data: vec![1, 2, 3] };
    // s包含一个Vec<i32>,当s超出作用域,Vec<i32>占用的堆内存会被释放
}
  1. 结构体借用
    • 可以通过借用结构体实例来访问其字段,而不转移所有权。
    • 示例:
struct MyStruct {
    data: Vec<i32>
}

fn print_struct(s: &MyStruct) {
    for num in &s.data {
        println!("{}", num);
    }
    // s是借用的,函数结束后,s所引用的结构体实例所有权仍在调用者手中
}

fn main() {
    let s = MyStruct { data: vec![1, 2, 3] };
    print_struct(&s);
    // s仍然有效,因为所有权未转移
}