如何找到变量并且是在恒定时间内完成的

how are variables found and is it done in constant time

我最近在想的一件事是计算机如何找到他的变量。当我们 运行 一个程序时,该程序将在堆栈中创建多个层,它打开的每个新作用域都有一层,并在该作用域的堆中存储变量值或指针。当范围完成时,它和它的所有变量都将被销毁。但是计算机如何知道它的变量在哪里呢?如果相同的变量更频繁地出现,则使用哪些变量。

我怎么想的,计算机像数组一样搜索它所在的范围,如果找不到变量,它就会像链表一样向下跟随堆栈,并像数组一样搜索下一个范围。

这导致全局变量使用起来最慢的假设,因为它必须一直遍历回到最后一个范围。所以它有一个来自 a * n 的计算时间(a = 每个范围的平均变量数量,n = 范围数量)。如果我现在假设我的代码是递归的并且在全局变量的递归函数调用中(假设我已经定义了变量 const PI = 3.1416 并且我在每次递归中都使用它),那么它将再次向后遍历它单次调用,如果我的递归函数需要 1000 次递归,那么它会执行 1000 次。

但另一方面,在学习递归的过程中,我从来没有听说过要尽可能避免引用不在递归范围内的变量。因此,我想知道我的想法是否正确。有人可以阐明这个问题吗?

反过来说:作用域、框架、堆不产生变量,变量产生作用域、框架、堆。
两者实际上都有点牵强,但我的观点是避免关注变量的生命周期(这就是堆和堆栈等术语的真正含义),而是深入了解一下。

内存是一种存储形式,其中每个单元格都分配有一个数字,单元格称为word,数字称为address .
地址集合称为地址space,一个地址space通常是地址范围或地址范围的并集。

编译器假定程序数据将加载到特定地址,比如X,并且在X[=99=之后有足够的内存](即 X+1,X+2,X+3,...,全部存在)对于所有数据。
然后变量从 X 开始按顺序排列,编译器的工作是保持地址 X+[=30= 之间的关联]k
和变量 instance.
请注意,一个变量可能会被多次实例化,调用一个函数两次或递归都是这样的例子。
在第一种情况下,两个实例可以共享相同的地址 X+k 因为它们在时间上不重叠(到第二个还活着,第一个结束了)。
在第二种情况下,两个实例在时间上重叠,必须使用两个地址。

所以我们看到是变量的生命周期影响了变量名和它的地址之间的映射(a.k.a。变量的分配 ) 完成。
两种常见的策略是:

  • 一叠
    我们从地址 X+b 开始,并在连续地址 X+[=30 分配新实例=]b+1、X+b+2等
    当前地址(例如X+b+54)存储在某处(它是堆栈指针)。
    当我们想要释放一个变量时,我们将堆栈指针设置回来(例如从 X+b+54 到 X+b+53).
    我们可以看到,释放一个不是最后分配的变量是不可能的。
    这允许 非常 快速 allocation/deallocation 并且自然地满足保存局部变量的函数框架的需要:当函数被调用时,新变量被分配,当它结束时他们被删除了。
    从上面我们注意到的,我们看到如果 f 调用 g (即 fg) 那么 f 的变量不能在 g.
    的变量之前被释放 这又很自然地符合函数的语义。


  • 该策略动态在地址X+o.
    分配一个变量实例 运行时保留一个地址块并管理它们的状态(空闲,已占用),当被询问时,它可以提供一个空闲地址并将其标记为已占用。
    例如,这对于分配大小取决于用户输入的对象很有用。

  • 堆(静态)
    一些变量具有程序的生命周期,但它们的大小和数量在编译时已知。
    在这种情况下,编译器简单地为每个实例分配一个唯一的地址 X+i.
    它们不能被释放,它们与程序代码一起被批量加载到内存中,并一直呆在那里直到程序被卸载。

我留下了一些细节,例如堆栈通常从大地址向低地址增长(因此它可以放在内存的最远边缘)以及变量占用多个地址这一事实。
一些编程语言,尤其是解释型编程语言,不会将地址与变量实例相关联,相反,它们会在变量名(适当限定的)和变量值之间保留一个映射,这样可以通过许多特定方式控制变量的生命周期(参见 Javascript 中的闭包)。

全局变量分配在静态堆中,只有一个实例存在(只有一个地址)。
每个使用它的递归函数总是直接引用唯一的实例,因为唯一地址在编译时已知。
函数中的局部变量在堆栈中分配,函数的每次调用(递归或非递归)都使用一组新的实例(地址不必每次都相同,但可以)。

简单地说,没有查找,分配变量以便代码可以在编译器一次访问它们(相对地,在堆栈中,或绝对地,在堆中)。