堆栈如何按照与创建时不同的顺序处理弹出值?

Ton*_*ony 2 stack rust

我无法想到如何有效地标题,这可能就是为什么我找不到一个好的搜索.在Rust编程书中理解堆与堆栈时,我读到:

"数据类型"部分中涵盖的类型都存储在堆栈中,并在其作用域结束时从堆栈中弹出.

我阅读了更深入的区别,但我仍然很好奇如何在可以定义变量并在以后使用数百行时使用堆栈.举个例子:

let x = 5;
let y = 6;
println!("x = {}", x); // cannot just pop x off here as y was pushed last?
Run Code Online (Sandbox Code Playgroud)

澄清一下,堆栈是否用于作用域,但是在每个作用域内部还必须有一个堆来知道运行时的位置?

这是否意味着堆栈也在堆上分配?或者编译器是否将这两者完全分开?

对不起,如果这变成了一般关于编译器和内存管理的问题.

fn main() {                  // if this is all a stack
    let one = 1;             // stack.push(one)
    let two = 2;             // stack.push(two) 

    let three = one + two;   // stack.push(stack.pop() + stack.pop())???
}
Run Code Online (Sandbox Code Playgroud)

这有意义吗?我来自Python,所以请耐心等待.

She*_*ter 5

这个引用有点快而且松散,因为它只是作为第一个估计.来自同一页面的不同引用更准确(强调我的):

当我们的代码调用一个函数时,传递给函数的值(可能包括指向堆上数据的指针)和函数的局部变量会被压入堆栈.当函数结束时,这些值将从堆栈中弹出.

您可能熟悉的"堆栈"(数据结构)与此处引用的"堆栈"(函数调用)有很大不同.

在堆栈(数据结构)中,您只能访问堆栈顶部的值.使用堆栈(函数调用),您可以从顶部保留/返回空间,但您可以访问堆栈中的任何内存.这可以是同一函数中的任何变量,甚至是来自调用函数的变量.硬件不关心这些细节; 由语言决定只有在有效时才能访问堆栈中的项目.

重要的是要认识到变量的没有被压入堆栈; 堆栈仅对应于存储变量的内存.

对堆栈大小的修改仅发生在函数入口和出口处.它也是一个巨大的块; 堆栈指针递增并递减所有函数的局部变量的总大小,而不管其范围如何.单个变量永远不会弹出或移出堆栈.

Rust的范围规则阻止您使用已移动的值,但实际上它们仍然位于堆栈中.您可以使用unsafe代码访问它们(但您不应该):

struct NonCopyNumber(i32);

fn example() {
    // We allocate space on the stack for all local variable
    // when we enter the function. There's 4 in this example.

    let value1 = NonCopyNumber(1); 

    let raw_ref1 = &value1.0 as *const i32;
    let raw_ref2;

    {
        let value2 = NonCopyNumber(2); 
        raw_ref2 = &value2.0 as *const i32;
    }

    drop(value1);

    // println!("{}", value1.0); // use of moved value: `value1.0`
    // println!("{}", value2.0); // Can't find value

    // Not really safe; assumes details about stack management.
    unsafe {
        println!("{}", *raw_ref1); // 1
        println!("{}", *raw_ref2); // 2
    }

    // Stack is popped
}
Run Code Online (Sandbox Code Playgroud)

内部括号组创建一个单独的堆栈框架

不,堆栈帧在函数入口处创建

堆栈框架(又称范围)

栈帧是一样的范围.输入函数会创建一个新范围并引入堆栈框架.一对花括号创建一个新的范围但引入堆栈框架.

DOE中value2被在到达闭用内撑杀出?

不,堆栈帧在功能出口处弹出.


让我们用这个具体的例子:

fn main() {
    let one: i32 = 1;
    let two: i32 = 2; 

    let three: i32 = one + two;
}
Run Code Online (Sandbox Code Playgroud)

我将用堆栈指针变量注释堆栈%sp.堆栈指针代表堆栈的当前顶部.

fn main() {
    // This function has 3 `i32` variables. 
    // Each is 4 bytes so the function requires 12 
    // bytes of stack space total.

    let one;   // Stored in memory at %sp + 0 bytes
    let two;   // Stored in memory at %sp + 4 bytes
    let three; // Stored in memory at %sp + 8 bytes

    %sp += 12; // Increment / push the stack

    one = 1;
    two = 2; 

    three = one + two;

    // Done with all our variables.

    %sp -= 12; // decrement / pop the stack 
}
Run Code Online (Sandbox Code Playgroud)

所有变量的空间都保留在函数的开头,然后在函数结束时将所有空间返回到堆栈,在这两种情况下都是一次性的.

值得注意的是,我们可以添加许多额外的括号,但仍然得到相同的堆栈注释结果:

fn main() {
    let one: i32 = 1;
    {
        let two: i32 = 2;
        {
            let three: i32 = one + two;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

只有语言的语义才能阻止我们在变量超出范围之后或初始化之前使用变量.


这是否意味着堆栈也在堆上分配?

这是一个非常难以回答的问题.在计算机1中,您只有一块内存:那些RAM芯片插入主板.您的所有程序数据最终都存储在此处,无论是"堆栈"还是"堆栈".两者之间的最大区别是他们的访问模式.

如上所示,堆栈非常轻巧且高性能但不灵活.您可以在堆栈中保留更多空间(调用函数)并将该空间返回到堆栈(保留函数),这就是它.

堆更灵活,但速度更慢,更复杂,需要额外的簿记.

通常,堆栈和堆在内存中朝向彼此增长.一个从零开始向上增长,另一个从MAX开始向下增长.

然后你会像线程一样深入细节.这些可能从堆中分离出一块内存,然后将这些内存用于自己的堆栈.所以,从技术上来说,是:有时一个堆栈里面一个堆.

使其变得更加复杂,实际上可以存在多个堆,每个堆根据它们自己的规则来管理存储器.

1不是所有的计算机,而是人们日常计划的绝大多数计算机.缓存和其他内容也有不同的内存级别,但这些内容并没有发挥作用.


println!("x = {}", x); // cannot just pop x off here as y was pushed last?
Run Code Online (Sandbox Code Playgroud)

使用变量in println不会移动它,所以即使你的原始前提是真的,它也不会在这里发挥作用.请参阅println!借用或拥有变量?