C 堆栈如何在递归中工作

C 堆栈如何在递归中工作,c,C,我不明白堆栈在递归中是如何工作的。在递归过程中,函数的参数被推送到堆栈中,返回地址也被推送到堆栈中。返回地址和参数被推到同一个堆栈中,或者返回地址被推到另一个堆栈中?任何一种方法都是可能的。结果是相当的。函数的局部变量也需要像堆栈一样管理的空间,这可以是相同的堆栈,也可以是不同的堆栈。这是实现C函数调用的一种可能方法: 将函数参数推送到参数堆栈中 将所需的返回地址(通常是分支后的指令地址)推送到返回地址堆栈 分支到函数的地址 将函数局部变量的初始值推送到局部变量堆栈 以及从呼叫返回的相应方式

我不明白堆栈在递归中是如何工作的。在递归过程中,函数的参数被推送到堆栈中,返回地址也被推送到堆栈中。返回地址和参数被推到同一个堆栈中,或者返回地址被推到另一个堆栈中?

任何一种方法都是可能的。结果是相当的。函数的局部变量也需要像堆栈一样管理的空间,这可以是相同的堆栈,也可以是不同的堆栈。这是实现C函数调用的一种可能方法:

  • 将函数参数推送到参数堆栈中
  • 将所需的返回地址(通常是分支后的指令地址)推送到返回地址堆栈
  • 分支到函数的地址
  • 将函数局部变量的初始值推送到局部变量堆栈
以及从呼叫返回的相应方式:

  • 从局部变量堆栈中弹出局部变量
  • 从返回地址堆栈中弹出
  • 分支到刚才读取的地址
  • 从参数堆栈中弹出参数
如果没有三个独立的堆栈,只有一个,那么上面的过程仍然有效。请注意,它之所以有效,是因为步骤顺序正确:对于单个堆栈,您需要按照与推送相反的顺序执行POP,而对于多个堆栈,顺序只需要在每个堆栈中保持一致

实际上,大多数平台都使用单个堆栈来处理所有内容,因为它使内存管理更容易。在调用函数之前,代码通过将参数和返回地址都推送到单个堆栈来创建一个堆栈。在返回地址之前推送参数通常比较容易,因为它使用相对寻址模式来获取返回地址:

push parameter_1
push parameter_2
…
push program_counter + 2
branch my_function
; first instruction after returning
函数的代码要做的第一件事是扩展堆栈框架,为其局部变量腾出空间。具体来说,“扩展堆栈帧”通常意味着向指向堆栈顶部的寄存器添加所需的空间。然后,在函数末尾,代码将返回地址加载到寄存器中,从堆栈指针中减去堆栈帧的长度,并分支到返回地址

有很多可能的变化和实际的复杂性。调用函数的确切方法称为。大多数平台都定义了调用约定,以便使用一个编译器编译的代码可以调用使用另一个编译器编译的函数。对于具有不同原型的函数,调用约定可能不同:例如,一些参数通常在寄存器中传递,对于可变函数和非可变函数,堆栈帧的布局可能不同。但是,一些平台支持多种调用约定,这需要在函数原型上添加额外的非标准注释(例如Windows上的vs)

其中一个可能的并发症是一个严重的问题。大多数平台使用单堆栈,因为它更容易实现,并且内存管理开销更少。但是,单个堆栈的缺点是,函数中的错误(例如存储在堆栈上的数组中的错误)很容易导致它覆盖返回地址。卷影堆栈是与主堆栈分离的返回地址堆栈中返回地址的额外副本。从函数返回时,代码检查返回地址的两个副本是否相同,如果不相同,则跳转到错误处理程序。将返回地址保留在主堆栈中的原因是为了兼容性。被调用函数将返回地址推送到返回地址堆栈,而不是调用方;这样,调用方就不需要知道它调用的函数是否使用影子堆栈支持编译。被调用的函数从主堆栈获取返回地址