MST

星途 面试题库

面试题:Rust中指针与引用在内存管理上的主要区别

请详细阐述在Rust语言里,指针(如Box指针等)与引用在内存管理方面存在哪些主要的不同点,并且举例说明在何种场景下会因为这种区别导致不同的内存行为。
29.1万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

指针(以Box指针为例)与引用在内存管理方面的主要不同点

  1. 所有权
    • Box指针:Box拥有其所指向的数据的所有权。当Box离开作用域时,它所指向的数据会被自动释放。例如:
    {
        let b = Box::new(5);
        // b拥有5的所有权
    }
    // 这里b离开作用域,Box所指向的5会被释放
    
    • 引用:引用不拥有数据的所有权。它只是借用数据,数据的所有权仍归原所有者。例如:
    let a = 5;
    let r = &a;
    // r借用a,a仍拥有5的所有权
    
  2. 内存分配与释放
    • Box指针:Box通常在堆上分配内存(除非数据非常小,可能会进行栈上分配优化)。当Box被销毁时,它所分配的堆内存会被释放。例如:
    let b = Box::new([1; 10000]);
    // 在堆上分配了一个包含10000个1的数组
    
    • 引用:引用本身不分配额外的内存,它只是指向已存在的数据。它不会影响数据的内存释放,数据的释放由其所有者决定。例如:
    let s = String::from("hello");
    let r = &s;
    // r不分配新内存,只是指向s的数据
    
  3. 可变性
    • Box指针:Box本身可以是可变的(mut),从而允许修改其所指向的数据。例如:
    let mut b = Box::new(5);
    *b = 6;
    
    • 引用:分为不可变引用(&T)和可变引用(&mut T)。不可变引用不能修改数据,可变引用在同一时间只能有一个,以确保数据一致性。例如:
    let mut s = String::from("hello");
    let r1 = &mut s;
    r1.push('!');
    // 此时不能再创建另一个可变引用,若要创建不可变引用也不行,直到r1离开作用域
    

因区别导致不同内存行为的场景举例

假设有一个结构体MyStruct

struct MyStruct {
    data: i32
}
  1. 使用Box指针的场景
    fn box_example() {
        let b = Box::new(MyStruct { data: 5 });
        // 这里Box在堆上分配了MyStruct实例
        // 函数结束时,Box离开作用域,MyStruct实例的内存被释放
    }
    
  2. 使用引用的场景
    fn ref_example() {
        let s = MyStruct { data: 5 };
        let r = &s;
        // r不分配新内存,只是引用s
        // 函数结束时,s离开作用域,其内存被释放,r只是借用,不影响释放行为
    }
    
    在更复杂的场景中,如果有一个函数接收Box指针并返回Box指针,Box的所有权会转移:
    fn take_box(b: Box<MyStruct>) -> Box<MyStruct> {
        // b的所有权转移到函数中
        let new_b = Box::new(MyStruct { data: b.data + 1 });
        new_b
    }
    
    而使用引用时,函数接收的只是借用:
    fn borrow_ref(r: &MyStruct) {
        // r借用MyStruct实例
        println!("Data: {}", r.data);
    }
    
    这里函数borrow_ref不能修改r指向的数据(如果r是不可变引用),也不会影响数据的内存管理,因为所有权仍在原所有者手中。而take_box函数中,Box的所有权发生转移,新返回的Box也会在其作用域结束时释放内存。