堆栈的目的是什么?为什么我们需要它? [英] What is the purpose of a stack? Why do we need it?

查看:29
本文介绍了堆栈的目的是什么?为什么我们需要它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我现在正在学习 MSIL 以学习调试我的 C# .NET 应用程序.

So I am learning MSIL right now to learn to debug my C# .NET applications.

我一直想知道:堆栈的目的是什么?

只是把我的问题放在上下文中:
为什么要从内存传输到堆栈或加载"?另一方面,为什么要从堆栈转移到内存或存储"?为什么不把它们都放在内存中?

  • 是不是因为它更快?
  • 是不是因为它是基于 RAM 的?
  • 为了效率?

我试图掌握这一点,以帮助我更深入地理解 CIL 代码.

I'm trying to grasp this to help me understand CIL codes much more deeply.

推荐答案

更新:我非常喜欢这个问题所以我做了它2011 年 11 月 18 日我博客的主题.谢谢你的好问题!

UPDATE: I liked this question so much I made it the subject of my blog on November 18th 2011. Thanks for the great question!

我一直想知道:堆栈的目的是什么?

I've always wondered: what is the purpose of the stack?

我假设您指的是 MSIL 语言的评估堆栈,而不是运行时的实际每线程堆栈.

I assume you mean the evaluation stack of the MSIL language, and not the actual per-thread stack at runtime.

为什么要从内存转移到堆栈或加载"?另一方面,为什么要从堆栈转移到内存或存储"?为什么不把它们都放在内存中?

Why is there a transfer from memory to stack or "loading?" On the other hand, why is there a transfer from stack to memory or "storing"? Why not just have them all placed in the memory?

MSIL 是一种虚拟机"语言.像 C# 编译器这样的编译器会生成 CIL,然后在运行时生成另一个称为 JIT(Just In Time)的编译器) 编译器将 IL 转换为可以执行的实际机器代码.

MSIL is a "virtual machine" language. Compilers like the C# compiler generate CIL, and then at runtime another compiler called the JIT (Just In Time) compiler turns the IL into actual machine code that can execute.

所以首先让我们回答为什么要使用 MSIL?"这个问题.为什么不让 C# 编译器写出机器代码?

So first let's answer the question "why have MSIL at all?" Why not just have the C# compiler write out machine code?

因为这样做更便宜.假设我们没有那样做;假设每种语言都必须有自己的机器代码生成器.您有二十种不同的语言:C#、JScript .NET、Visual Basic、IronPython, F#... 假设您有十个不同的处理器.您必须编写多少个代码生成器?20 x 10 = 200 个代码生成器.这是很多工作.现在假设您要添加一个新处理器.您必须为它编写二十次代码生成器,每种语言编写一次.

Because it is cheaper to do it this way. Suppose we didn't do it that way; suppose each language has to have its own machine code generator. You have twenty different languages: C#, JScript .NET, Visual Basic, IronPython, F#... And suppose you have ten different processors. How many code generators do you have to write? 20 x 10 = 200 code generators. That's a lot of work. Now suppose you want to add a new processor. You have to write the code generator for it twenty times, one for each language.

此外,这是一项艰巨而危险的工作.为您不是专家的芯片编写高效的代码生成器是一项艰巨的工作!编译器设计人员是语言语义分析方面的专家,而不是新芯片组的有效寄存器分配方面的专家.

Furthermore, it is difficult and dangerous work. Writing efficient code generators for chips that you are not an expert on is a hard job! Compiler designers are experts on the semantic analysis of their language, not on efficient register allocation of new chip sets.

现在假设我们按照 CIL 的方式来做.您必须编写多少个 CIL 生成器?一种语言.您必须编写多少个 JIT 编译器?每个处理器一个.总计:20 + 10 = 30 个代码生成器.而且,由于CIL是一种简单的语言,所以语言到CIL生成器很容易编写,而CIL到机器代码生成器也很容易编写,因为CIL是一种简单的语言.我们摆脱了 C# 和 VB 以及诸如此类的所有错综复杂的事物,并将所有内容降低"为一种易于编写抖动的简单语言.

Now suppose we do it the CIL way. How many CIL generators do you have to write? One per language. How many JIT compilers do you have to write? One per processor. Total: 20 + 10 = 30 code generators. Moreover, the language-to-CIL generator is easy to write because CIL is a simple language, and the CIL-to-machine-code generator is also easy to write because CIL is a simple language. We get rid of all of the intricacies of C# and VB and whatnot and "lower" everything to a simple language that is easy to write a jitter for.

拥有一种中间语言可以显着降低生产新语言编译器的成本.它还大大降低了支持新芯片的成本.你想支持一个新的芯片,你在那个芯片上找到一些专家,让他们写一个 CIL 抖动,你就完成了;然后,您可以在芯片上支持所有这些语言.

Having an intermediate language lowers the cost of producing a new language compiler dramatically. It also lowers the cost of supporting a new chip dramatically. You want to support a new chip, you find some experts on that chip and have them write an CIL jitter and you're done; you then support all those languages on your chip.

好的,我们已经确定了为什么我们有 MSIL;因为拥有中间语言可以降低成本.为什么语言是堆栈机器"?

OK, so we've established why we have MSIL; because having an intermediate language lowers costs. Why then is the language a "stack machine"?

因为堆栈机器在概念上对于语言编译器编写者来说非常容易处理.堆栈是一种用于描述计算的简单、易于理解的机制.对于 JIT 编译器编写者来说,堆栈机器在概念上也很容易处理.使用堆栈是一种简化的抽象,因此再次降低我们的成本.

Because stack machines are conceptually very simple for language compiler writers to deal with. Stacks are a simple, easily understood mechanism for describing computations. Stack machines are also conceptually very easy for JIT compiler writers to deal with. Using a stack is a simplifying abstraction, and therefore again, it lowers our costs.

你会问为什么要有堆栈?"为什么不直接从内存中做所有事情?好吧,让我们考虑一下.假设您要为以下各项生成 CIL 代码:

You ask "why have a stack at all?" Why not just do everything directly out of memory? Well, let's think about that. Suppose you want to generate CIL code for:

int x = A() + B() + C() + 10;

假设我们有这样的约定,即添加"、调用"、存储"等总是从堆栈中取出它们的参数并将它们的结果(如果有的话)放在堆栈上.要为这个 C# 生成 CIL 代码,我们只需说:

Suppose we have the convention that "add", "call", "store" and so on always take their arguments off the stack and put their result (if there is one) on the stack. To generate CIL code for this C# we just say something like:

load the address of x // The stack now contains address of x
call A()              // The stack contains address of x and result of A()
call B()              // Address of x, result of A(), result of B()
add                   // Address of x, result of A() + B()
call C()              // Address of x, result of A() + B(), result of C()
add                   // Address of x, result of A() + B() + C()
load 10               // Address of x, result of A() + B() + C(), 10
add                   // Address of x, result of A() + B() + C() + 10
store in address      // The result is now stored in x, and the stack is empty.

现在假设我们没有堆栈.我们将按照您的方式进行,其中每个操作码都获取其操作数的地址和存储结果的地址:

Now suppose we did it without a stack. We'll do it your way, where every opcode takes the addresses of its operands and the address to which it stores its result:

Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...

你明白这是怎么回事吗?我们的代码变得巨大,因为我们必须显式分配所有的临时存储通常按照惯例只会放在堆栈上.更糟糕的是,我们的操作码本身都变得越来越庞大,因为它们现在都必须将要写入结果的地址和每个操作数的地址作为参数.一个添加"指令知道它将从堆栈中取出两个东西并将一个东西放在上面,可以是单个字节.需要两个操作数地址和一个结果地址的加法指令将非常庞大.

You see how this goes? Our code is getting huge because we have to explicitly allocate all the temporary storage that would normally by convention just go on the stack. Worse, our opcodes themselves are all getting enormous because they all now have to take as an argument the address that they're going to write their result into, and the address of each operand. An "add" instruction that knows that it is going to take two things off the stack and put one thing on can be a single byte. An add instruction that takes two operand addresses and a result address is going to be enormous.

我们使用基于堆栈的操作码,因为堆栈解决了常见问题.即:我想分配一些临时存储,很快使用它,然后在我完成后迅速摆脱它.通过假设我们有一个堆栈可供我们使用,我们可以使操作码非常小并且代码非常简洁.

We use stack-based opcodes because stacks solve the common problem. Namely: I want to allocate some temporary storage, use it very soon and then get rid of it quickly when I'm done. By making the assumption that we have a stack at our disposal we can make the opcodes very small and the code very terse.

更新:一些额外的想法

顺便说一句,通过 (1) 指定虚拟机、(2) 编写针对 VM 语言的编译器以及 (3) 在各种硬件上编写 VM 的实现来大幅降低成本的想法并不是什么新鲜事根本没有想法.它并非源自 MSIL、LLVM、Java 字节码或任何其他现代基础设施.我所知道的这种策略的最早实现是 1966 年的 pcode machine.

Incidentally, this idea of drastically lowering costs by (1) specifing a virtual machine, (2) writing compilers that target the VM language, and (3) writing implementations of the VM on a variety of hardware, is not a new idea at all. It did not originate with MSIL, LLVM, Java bytecode, or any other modern infrastructures. The earliest implementation of this strategy I'm aware of is the pcode machine from 1966.

我个人第一次听说这个概念是当我了解到 Infocom 实施者如何设法获得 Zork 在这么多不同的机器上运行得如此之好.他们指定了一个名为 Z-machine 的虚拟机,然后为所有人制作了 Z-machine 模拟器他们想要运行游戏的硬件.这带来了额外的巨大好处,即他们可以在原始 8 位系统上实现虚拟内存管理;游戏可能比内存大,因为他们可以在需要时从磁盘中分页代码,并在需要加载新代码时将其丢弃.

The first I personally heard of this concept was when I learned how the Infocom implementors managed to get Zork running on so many different machines so well. They specified a virtual machine called the Z-machine and then made Z-machine emulators for all the hardware they wanted to run their games on. This had the added enormous benefit that they could implement virtual memory management on primitive 8-bit systems; a game could be larger than would fit into memory because they could just page the code in from disk when they needed it and discard it when they needed to load new code.

这篇关于堆栈的目的是什么?为什么我们需要它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆