Recursion 变量是如何找到的?它是在固定时间内完成的吗

Recursion 变量是如何找到的?它是在固定时间内完成的吗,recursion,stack,computer-science,Recursion,Stack,Computer Science,我最近想的一件事是计算机如何找到他的变量。当我们运行一个程序时,程序将在堆栈中创建多个层,为它打开的每个新作用域创建一个层,并在该作用域中的堆中存储变量值或指针。当作用域完成时,它及其所有变量都将被销毁。但是计算机怎么知道它的变量在哪里呢?如果相同的变量出现的频率更高,那么应该使用哪些变量 在我的想象中,计算机像数组一样搜索它所在的范围,如果找不到变量,它会像链表一样沿着堆栈向下搜索,像数组一样搜索下一个范围 这导致了这样一种假设,即全局变量的使用速度最慢,因为它必须遍历到最后一个作用域。因此它

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

在我的想象中,计算机像数组一样搜索它所在的范围,如果找不到变量,它会像链表一样沿着堆栈向下搜索,像数组一样搜索下一个范围

这导致了这样一种假设,即全局变量的使用速度最慢,因为它必须遍历到最后一个作用域。因此它有一个从a*n开始的计算时间(a=每个作用域的平均变量数量,n=作用域数量)。如果我现在假设我的代码是递归的,并且在递归函数中调用一个全局变量(假设我已经定义了变量
const PI=3.1416
,并且我在每次递归中都使用它),那么每次调用它都会再次向后遍历它,如果我的递归函数需要1000次递归,然后它做了1000次


但另一方面,在学习递归时,我从来没有听说过,如果可能的话,应该避免引用在递归范围内找不到的变量。因此,我怀疑我的想法是否正确。有人能解释一下这个问题吗。

你是从另一个角度理解的:作用域、框架、堆不构成变量,变量构成作用域、框架、堆。
实际上,这两者都有点牵强,但我的观点是避免关注变量的生存期(这就是堆和堆栈等术语的真正含义),而是要深入研究

内存是一种存储形式,每个单元分配一个数字,该单元称为字,数字称为地址。
地址集称为地址空间,地址空间通常是地址范围或地址范围的并集

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

因此,我们发现变量的生存期影响变量名与其地址之间的映射(也称为变量分配)的完成方式。
两种常见的策略是:

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


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

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

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

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


简单地说,不需要查找,分配变量是为了让代码可以在编译器中访问它们(相对地,在堆栈中,或者绝对地,在堆中)

这实际上很有道理。谢谢你的回答,非常有帮助。