尾递归

1.什么是递归?

递归就是自己调用自己

2.编译器是如何实现递归的?

编译器是通过栈来实现递归,其实编译器也是通过栈来实现函数调用的,为了明白递归,我们先来看看我们的程序是如何实现函数调用的吧。

下面我们看一个函数调用的栗子

int adder(int x,int y)
{
 return x+y;
}

void call()
{
 int x=2;
 int y=3;
 adder(x,y);
}
 

我们使用gcc编译成汇编(PS:要想明白一个程序是怎么运行的最好的方式还是看汇编吧)

gcc -S test.c
 

下面我们看看汇编片段

        
.file   "recu.c"
        .text
        .globl  adder
        .type   adder, @function
adder:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    -8(%rbp), %eax
        movl    -4(%rbp), %edx
        addl    %edx, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   adder, .-adder
        .globl  call
        .type   call, @function
call:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    $2, -4(%rbp)
        movl    $3, -8(%rbp)
        movl    -8(%rbp), %edx
        movl    -4(%rbp), %eax
        movl    %edx, %esi
        movl    %eax, %edi
        call    adder
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1:
        .size   call, .-call
        .ident  "GCC: (GNU) 4.8.2"
        .section        .note.GNU-stack,"",@progbits
 

擦!汇编怎么那么多看不懂的符号啊?!!真拙计!

我们这里是r开头的,原因是我们使用的是64位的操作系统

为了读懂汇编,我还是先去复习了一下,首先先明白几个寄存器吧

在X86上,用户寄存器为eax, ebx, ecx, edx, esi, edi, ebp, esp 以及eipeax、ebx、ecx以及edx寄存器作为通用寄存器,可以用来进行临时存储

esi和edi可以用来存储,但对操作字符串类的函数有其他意义,在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串ebp通常用来容纳当前栈帧(stack frame)的内存地址,esp保存栈顶地址eip保存当前执行指令的内存地址。机器代码不能直接修改该寄存器,只能通过jmp和call指令族进行间接修改,实现循环,调用等

 

然后我们在明白一下重要术语:

栈帧:值得是ebp到esp的这一段内存区间,每一个函数的调用都会生成一个栈帧,这里面保存着函数里的变量和指令。

我们主要关心call函数以及adder函数,所以单独拿出来

我们先来看call函数:

****这里的栈类别是指的向下满栈
call:
.LFB1:
        .cfi_startproc                 ##这个表示函数的入口参见.cfi_startproc pushq   %rbp                   ##把rbp寄存器入栈
        .cfi_def_cfa_offset 16         ##定义CFI(call frame information)的cfa(Canonical Frame Address)的偏移量,主要是由于push了%rbp造成的偏移量
        .cfi_offset 6, -16             ##.cfi我也不明
        movq    %rsp, %rbp             ##把rsp的值搞到rbp中去,表示现在进入了新栈
        .cfi_def_cfa_register 6        ##说明现在的cfa register是rbp
        subq    $16, %rsp              ##空出两个单位,根据程序,我们应该猜得到是为x、y变量提供空间,为毛是压16个字节?难道是内存对其?
        movl    $2, -4(%rbp)           ##把rbp下面的一个4字节定义为x的空间
        movl    $3, -8(%rbp)           ##把rbp下面的第8个字节处定义成y的空间
        movl    -8(%rbp), %edx         ##把x、y的地址赋给寄存器edx和eax,这两个是通用寄存器
        movl    -4(%rbp), %eax
        movl    %edx, %esi             ##又把他们送个esi、edi这里是为传值做准备
        movl    %eax, %edi
        call    adder                  ##这里调用adder函数,同时这个指令暗含一个把当前栈顶压入栈
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
下面用图来说明
 

 

 好了,下面我们来谈谈尾递归。主要围绕下面一个方面1.什么是尾递归?与递归有什么区别?我们先来一段代码来看看这两者之间的区别 

int fact(int n){
      if(n <0) return 0;
      if(n ==0) return 1;
      if(n ==1) return 1;
      return n*fact(n-1);
}

int facttail(int n,int a){
      if(n <0) return 0;
      if(n ==0) return 1;
      if(n ==1) return a;

      return facttail(n-1,n*a);
}
 fact函数是递归版本,facttail函数是尾递归版本。 我们在文章最初知道了函数调用是通过栈帧来实现的,因为 栈帧中需要保存运算的变量,在使用递归的时候,比如函数fact,如果n非常大的话,很显然栈帧会越来越多,直到撑爆内存,这显然是我们不乐意见到的。但是我们来看factail这个函数,它也是使用递归来实现求n的阶乘,但是注意到,这个函数中, 其栈帧是没有变量需要保存的,这就是说我们的栈帧是不需要的,我们完全可以就在原来的栈帧上进行运算!!于是乎C编译器抖了个机灵,它如果发现了你使用了尾递归,他就会自动帮你优化,现在我们来看着两个函数的汇编。为了看着方便,我们把判断全部删除,如下 
int fact(int n){
      return n*fact(n-1);
}

int facttail(int n,int a){
      return facttail(n-1,n*a);
}
汇编代码如下:  
fact:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        subl    $1, %eax
        movl    %eax, %edi
        call    fact
        imull   -4(%rbp), %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   fact, .-fact
        .globl  facttail
        .type   facttail, @function
facttail:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    -4(%rbp), %eax
        imull   -8(%rbp), %eax
        movl    -4(%rbp), %edx
        subl    $1, %edx
        movl    %eax, %esi
        movl    %edx, %edi
        call    facttail
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
卧槽!怎么都是递归呢?和传说中主动优化的不一样呢? 原来是我搞忘记加入优化参数了,现在把优化参数加上,汇编代码如下 
.file   "fact.c"
        .text
        .p2align 4,,15
        .globl  fact
        .type   fact, @function
fact:
.LFB7:
        .cfi_startproc
        .p2align 4,,10
        .p2align 3
.L2:
        jmp     .L2
        .cfi_endproc
.LFE7:
        .size   fact, .-fact
        .p2align 4,,15
        .globl  facttail
        .type   facttail, @function
facttail:
.LFB8:
        .cfi_startproc
        .p2align 4,,10
        .p2align 3
.L4:
        jmp     .L4
        .cfi_endproc
.LFE8:
        .size   facttail, .-facttail
        .ident  "GCC: (GNU) 4.8.2"
        .section        .note.GNU-stack,"",@progbits
卧槽,我完全看不懂了,不过估计这东西优化成为了一个循环,看.L4,不过这个怎么把递归也搞成循环了?!这个真心不懂,求大家指教
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
城市应急指挥系统是智慧城市建设的重要组成部分,旨在提高城市对突发事件的预防和处置能力。系统背景源于自然灾害和事故灾难频发,如汶川地震和日本大地震等,这些事件造成了巨大的人员伤亡和财产损失。随着城市化进程的加快,应急信息化建设面临信息资源分散、管理标准不统一等问题,需要通过统筹管理和技术创新来解决。 系统的设计思路是通过先进的技术手段,如物联网、射频识别、卫星定位等,构建一个具有强大信息感知和通信能力的网络和平台。这将促进不同部门和层次之间的信息共享、交流和整合,提高城市资源的利用效率,满足城市对各种信息的获取和使用需求。在“十二五”期间,应急信息化工作将依托这些技术,实现动态监控、风险管理、预警以及统一指挥调度。 应急指挥系统的建设目标是实现快速有效的应对各种突发事件,保障人民生命财产安全,减少社会危害和经济损失。系统将包括预测预警、模拟演练、辅助决策、态势分析等功能,以及应急值守、预案管理、GIS应用等基本应用。此外,还包括支撑平台的建设,如接警中心、视频会议、统一通信等基础设施。 系统的实施将涉及到应急网络建设、应急指挥、视频监控、卫星通信等多个方面。通过高度集成的系统,建立统一的信息接收和处理平台,实现多渠道接入和融合指挥调度。此外,还包括应急指挥中心基础平台建设、固定和移动应急指挥通信系统建设,以及应急队伍建设,确保能够迅速响应并有效处置各类突发事件。 项目的意义在于,它不仅是提升灾害监测预报水平和预警能力的重要科技支撑,也是实现预防和减轻重大灾害和事故损失的关键。通过实施城市应急指挥系统,可以加强社会管理和公共服务,构建和谐社会,为打造平安城市提供坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值