在Rust中所有的值默认都是在栈上进行分配的。值也可以通过创建Box<T>在堆上进行分配。一个Box是一个智能指针,它指向T类型的值在堆上分配地址。当box离开作用域,就会调用它的销毁函数。它指向的内部对象也会被销毁,堆上分配的内存就被释放。

Box包含的值可以使用*操作符进行解引用;这会移除中间层。

use std::mem;


#[allow(dead_code)]

#[derive(Debug, Clone, Copy)]

struct Point {

    x: f64,

    y: f64,

}


// A Rectangle can be specified by where its top left and bottom right

// corners are in space

#[allow(dead_code)]

struct Rectangle {

    top_left: Point,

    bottom_right: Point,

}


fn origin() -> Point {

    Point { x: 0.0, y: 0.0 }

}


fn boxed_origin() -> Box<Point> {

    // Allocate this point on the heap, and return a pointer to it

    Box::new(Point { x: 0.0, y: 0.0 })

}


fn main() {

    // (all the type annotations are superfluous)

    // Stack allocated variables

    let point: Point = origin();

    let rectangle: Rectangle = Rectangle {

        top_left: origin(),

        bottom_right: Point { x: 3.0, y: -4.0 }

    };


    // Heap allocated rectangle

    let boxed_rectangle: Box<Rectangle> = Box::new(Rectangle {

        top_left: origin(),

        bottom_right: Point { x: 3.0, y: -4.0 },

    });


    // The output of functions can be boxed

    let boxed_point: Box<Point> = Box::new(origin());


    // Double indirection

    let box_in_a_box: Box<Box<Point>> = Box::new(boxed_origin());


    println!("Point occupies {} bytes on the stack",

             mem::size_of_val(&point));

    println!("Rectangle occupies {} bytes on the stack",

             mem::size_of_val(&rectangle));


    // box size == pointer size

    println!("Boxed point occupies {} bytes on the stack",

             mem::size_of_val(&boxed_point));

    println!("Boxed rectangle occupies {} bytes on the stack",

             mem::size_of_val(&boxed_rectangle));

    println!("Boxed box occupies {} bytes on the stack",

             mem::size_of_val(&box_in_a_box));


    // Copy the data contained in `boxed_point` into `unboxed_point`

    let unboxed_point: Point = *boxed_point;

    println!("Unboxed point occupies {} bytes on the stack",

             mem::size_of_val(&unboxed_point));

}
代码解析

这段代码主要演示了Rust中栈的分配和对分配(通过Box<T>)的基本概念和内存占用情况。

栈分配

默认行为:像Point和rectangle这样的局部变量都是直接在栈上分配

栈分配速度快,但是声明周期通常与作用域绑定,空间有限。

堆分配

使用Box::new(value)可以将值value移动到堆上分配,并返回一个智能指针Box<T>,这个指针本身存储在栈上。

Box<T>是一个智能指针,它拥有其所指向的堆数据的所有权。

当Box<T>离开了作用域时,其Drop接口会被调用,负责释放堆上的内存,防止内存泄漏。

代码中创建了boxed_rectangle、boxed_point和box_in_a_box。

内存占用

mem::size_of_val(&val)用户获取值val在栈上所占用的字节数。

对于直接存储在栈上的类型(如point和rectangle),他返回该类型的大小。

Point(x:f64,y:f64):两个f64(每个8个字节),通常是16字节。

Rectangle{top_left:Point,bottom_right:Point}:两个Point,通常是32个字节。

对于Box<T>类型的变量,存储在栈上的这两个指针本身(通常是一个指针,在加上一些元数据),而不是整个T对象。在大多数系统上,指针的大小通常是8个字节(64位系统)。因此打印boxed_point、boxed_rectangle和box_in_a_box的大小都会是8个字节(指向堆数据的指针的大小),而不管它们指向的Point和Rectangle实际有多大。

box_in_a_box:Box<Box<Point>>的大小也是8个字节,因为它仍然是一个指针(执行另一个在堆上的Box<Point>)。

解引用

使用*操作符可以解引用Box<T>,获取其拥有的堆上的值。

let unboxed_point: Point = *boxed_point;这一行:

*boxed_point解引用boxed_point,得到它拥有的堆上的Point。

由于Point实现了Copy接口,这里会发生复制(copy),将堆上的Point数据复制一份到栈上的unboxed_point变量中。

如果Point没有实现copy,那么这行代码会发生移动(move),所有权会从堆转移到栈上的变量,之后boxed_point将不可再用。

总结

这段代码的核心目的是:

  1. 展示Rust中默认栈分配和现实堆分配(通过Box)的基本用法。
  2. 阐明Box<T>智能指针的本质:它是一个存储在栈上的指针,指向堆上分配的数据,并负责该数据的生命周期管理(自动释放)。
  3. 对比内存占用:直观地展示了直接栈分配的值和指向堆数据的Box指针在栈空间占用上的区别(后者为固定大小的指针)。
  4. 演示如何通过解引用操作符*来访问Box内部的值,并可能将其移出或复制到栈上。

Box在Rust中是最基本的智能指针,常用于一下场景:当你有一个其大小无法再编译时确定的数据(例如递归类型的数据),或者当你希望拥有一个大型数据避免在栈上拷贝时,或者当你需要实现一个特定trait类型的存在时(dyn trait)。

原文链接地址:Box, stack and heap - Rust By Example

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐