rust中stack AND heap 以及所有权机制

多数编程语言里,你不需要太关注堆和栈。但是在一个类似Rust这样的“系统级”编程语言中,一个值再栈中还是在堆中,对程序的行为有很大的影响,因此你必须作出明确的决定。

无论是栈还是堆,都是你的程序在运行时可用到的内存的一部分,但是他们被以不同的方式“组织/构造/安排”。中存储的值存储和获取是以后进先出的方式;增加数据成为入栈,删除数据成为出栈。

所有存储在栈中的数据必须拥有已知的固定的大小。编译时大小未知的数据或者运行时大小可能改变的数据必须存储在“堆”中。堆不像栈那么有条理/组织:当你往堆中存放数据时,你申请了一块儿确定大小的空间,“内存分配器”在堆中找到一块儿足够大的空未使用空间,并将其标记为已/在使用,然后返回指向该块内存地址的指针(或理解为返回内存起始地址和块大小)。这个过程称为“在堆中分配内存”,或简称为分配内存(allocating)。向栈中存储数据,不涉及到allocating动作。因为“指针”是一个“已知的,大小固定”的,你可以将指针存于栈中,但是当你需要真正的数据时,你必须找到跟随指针找到的相应的内存位置。

想象一下在餐馆里,你告诉服务员一共有几位,服务员找到一张可以容纳所有人的桌子并将你带到桌子那。当你的伙伴有人来晚了,他们可以询问你在哪并找到你。

往栈内存储数据比在堆中“分配内存”要更快,因为“分配器”不需要寻找存储新数据的地方(内存位置);栈的可操作位置始终在栈的顶部。相比之下,在堆中分配内存则需要更多的工作,首先分配器需要先在堆中找到一块足够存储数据的区域,然后记录内存起始位置等信息,由此来给下一次的分配提供足够的信息。

获取堆中的数据比获取栈中的数据要慢,因为你必须跟随一个指针才能最终获取到实际数据。现代处理器会更快如果他们更少的在内存中来回跳转。继续之前的类比,餐馆服务员要获取所有桌子(客人)的菜单。获取一个桌的所有菜单然后再去获取下一个桌的所有菜单会更有效率;找一个A桌的订单,然后再找一个B桌的订单,再找另一个A桌的订单,这样来回跳着找,肯定会更慢。同样,处理器工作在连续的/相近的(比如在栈中)数据上时会比在较远的位置来回跳转时更快(比如在堆中)。在堆中分配一块较大内存,同样会消耗更多时间。

当代码调用函数时,传递进函数的值(包括潜在的指向堆中数据的指针)以及函数内局部变量被推入到栈中,当函数结束,这些值被移除栈。

对哪些代码用到堆内的数据保持关注,最小化堆内数据的重复度,清理堆中不使用的数据以方式OOM,这三点是“所有权”系统所要处理的。一旦你理解了“所有权”,你并不需要经常考虑栈和堆,但是理解 管理堆中数据是所有权存在的意义 可以帮助理解它的工作方式。


  1. 简单类型数据在栈中复制
let a=1;
let b=a;
//此处a和b均可用
//在栈中“复制”相应值,对应b
  1. 复杂类型数据在堆中,栈中存储其指针(包含内存位置,块大小等信息)
let a=String::from_string("a");
let b=a;
//此处b可用,a已经将所有权“move”给了b,a不再可用,b对应栈中的一个新的指针(指向堆中数据)
//上述过程,堆中数据不变(不会复制)
  1. 所有权和函数, 观察下面例子中对象的所有权在变量和函数之间转移/move的过程
fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
  1. 引用&,引用不获取对象的所有权,所以“引用”在离开作用域时不删除对象。又可理解为带引用符&的变量暂时“借用”了所有权,当它离开作用域时,归还所有权
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
posted @ 2021-12-09 10:13  jkklee  阅读(181)  评论(0)    收藏  举报