昨天被问到了尾递归及编译器对它的处理相关,一直对它没有研究过,解释得很含糊。
回来查了下,记录如下:
递归有线性递归(普通的递归)和尾递归。
由于尾递归的特殊性,一般的编译器会做些特殊处理。因此,在效率和开销上,比普通递归好。
举个例子,计算n!
1)线性递归:
type recurve(long n)
{
return (n == 1) ? 1 : n * recurve(n - 1);
}
2)尾递归:
type recurve_tail(long n, long result)
{
return (n == 1) ? result : recurve_tail(n - 1, result * n);
}
再封装成1)的形式:
type recurve(long n)
{
return (n == 0) ? 1 : recurve_tail(n, 1);
}
分析:
很容易看出, 普通的线性递归比尾递归更加消耗资源。每次调用都使得调用链条不断加长,系统不得不开辟新的栈进行数据保存和恢复;而尾递归就
不存在这样的问题, 因为他的状态完全由n 和 a 保存,并且,被调用函数返回的值即为要求的值,本函数再没有作用,于是本函数不再保存,直接在本函数堆栈上进行递归调用,
对于特殊情况,甚至可以不使用内存空间,直接在寄存器完成。
编译器如何判断是否尾递归?
返回的值是函数本身,没有其它选择。
看一下上述尾递归函数在gcc 4.3.2-1-1下未进行优化的编译结果:
1
.file
"rec
.c
"
2
.text
3
.globl
recurve_tail
4
.type
recurve_tail
, @function
5
recurve_tail
:
6
pushl
%ebp
7
movl
%esp
, %ebp
8
subl
$24
, %esp
9
cmpl
$1
, 8
(%ebp
)
10
je
.L2
11
movl
12
(%ebp
), %eax
12
movl
%eax
, %edx
13
imull
8
(%ebp
), %edx
14
movl
8
(%ebp
), %eax
15
subl
$1
, %eax
16
movl
%edx
, 4
(%esp
)
17
movl
%eax
, (%esp
)
18
call
recurve_tail
19
movl
%eax
, -4
(%ebp
)
20
jmp
.L3
21
.L2
:
22
movl
12
(%ebp
), %eax
23
movl
%eax
, -4
(%ebp
)
24
.L3
:
25
movl
-4
(%ebp
), %eax
26
leave
27
ret
28
.size
recurve_tail
, .-recurve_tail
29
.ident
"GCC
: (Debian
4
.3
.2
-1
.1
) 4
.3
.2
"
30
.section
.note.GNU
-stack
,"",@progbits
未进行优化,与普通递归处理方式相同,新开辟了栈;再看-O3优化结果:
1
.file
"rec
.c
"
2
.text
3
.p2align
4
,,15
4
.globl
recurve_tail
5
.type
recurve_tail
, @function
6
recurve_tail
:
7
pushl
%ebp
8
movl
%esp
, %ebp
9
movl
8
(%ebp
), %edx
10
movl
12
(%ebp
), %eax
11
cmpl
$1
, %edx
12
je
.L2
13
.p2align
4
,,7
14
.p2align
3
15
.L5
:
16
imull
%edx
, %eax
17
subl
$1
, %edx
18
cmpl
$1
, %edx
19
jne
.L5
20
.L2
:
21
popl
%ebp
22
ret
23
.size
recurve_tail
, .-recurve_tail
24
.ident
"GCC
: (Debian
4
.3
.2
-1
.1
) 4
.3
.2
"
25
.section
.note.GNU
-stack
,"",@progbits
此时,正如上面分析,一直在本空间计算,未开辟新栈。
分享到:
相关推荐
7. **尾递归**:在某些编译器或解释器支持的情况下,可以使用尾递归优化。尾递归是指递归调用是函数体的最后一个操作,且返回值不依赖于递归调用的结果。这种情况下,编译器可能能够优化掉不必要的栈空间分配,降低...
然而,标准C语言并不保证尾递归优化。 总之,理解并熟练掌握C语言中的递归函数,可以极大地提升编程能力,解决复杂问题。但在实际应用中,应谨慎考虑递归的效率和适用性,根据具体情况选择合适的方法。
为避免这种情况,可以考虑使用尾递归优化或者改写为迭代方式。 3. 递归的应用场景 - 数学问题:如计算阶乘、斐波那契数列等。 - 数据结构:在处理树和图等数据结构时,如遍历二叉树、深度优先搜索(DFS)等。 - ...
C语言是一种通用的编程语言,由丹尼斯·里奇(Dennis Ritchie)在20世纪70年代早期于美国电话电报公司(AT&T)的贝尔实验室开发。C语言以其高效性、灵活性和可移植性而闻名,它是一种过程式编程语言,提供了对底层...
尾递归是一种优化的递归形式,它可以被编译器或解释器有效地处理,以减少内存栈的需求。在这个场景中,我们将深入探讨如何使用尾递归来实现整数到二进制的转换。 首先,让我们了解什么是尾递归。尾递归是指在一个...
尾递归是一种特殊的递归形式,如果递归调用是函数体中的最后一个动作,则编译器优化时可以将递归调用转变为跳转,避免新帧的创建,从而降低空间消耗。 文章作者刘卓亚是云南机电职业技术学院工业信息技术系的讲师,...
同时,通过尾递归优化(如果编译器支持)或使用迭代方法,可以提高递归函数的效率。 总结,"C语言递归调戏谷歌地球"这一主题结合了基础的编程概念与实际的GIS应用。理解和掌握递归是提升C语言编程能力的关键步骤,...
在尾递归中,由于每次递归调用后没有其他语句执行,编译器可以优化递归过程,避免创建新的堆栈帧,从而减少内存消耗。在某些情况下,尾递归可以被转换为循环,以便提高效率。 递归函数必须有一个明确的终止条件,以...
3. 考虑尾递归优化,尾递归是指在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样的函数调用是最后一项操作,编译器或解释器可以进行优化,使递归本身无论调用多少次,都只占用一个栈帧,不会...
此时,可以考虑使用迭代的方法或者其他算法优化技术,如使用尾递归优化、记忆化递归等方法来减少递归调用的开销。在编写复杂的递归算法时,必须仔细测试和验证算法的正确性和效率,确保递归程序在各种情况下都能稳定...
- **尾递归**:在函数返回的时候,调用自身,并且return语句不能包含表达式。这种情况下,编译器或解释器可以进行优化,使递归调用的开销尽可能小。 3. **常见的递归问题** - **阶乘计算**:递归地计算一个数的...
尾递归是指在函数返回时,调用自身本身,并且return语句不能包含表达式。编译器或解释器可以优化尾递归,使其只占用常量空间。 了解了这些基础知识后,我们还可以进一步研究如何使用递归来解决其他问题,如搜索、...
- 谨慎处理递归深度,避免栈溢出,可以考虑使用尾递归优化或迭代法代替。 - 在递归过程中,确保每次调用都是对问题规模的减小,否则可能无法收敛到正确答案。 总之,函数的递归调用是C语言中一个重要的编程技巧,...
此外,合理地使用尾递归优化,可以降低递归带来的开销,尾递归是指在递归调用的最后返回,而不需要进行其他操作,这种情况下,编译器或解释器有时可以优化掉额外的栈空间。 总结来说,C语言中的函数递归是一种强大...
**C语言中的尾递归**: C语言可以通过编译器选项 `-O2` 或更高的优化级别来启用尾递归优化。当编译器检测到函数是尾递归的形式时,它会在编译期间进行相应的优化,例如转换为循环结构或者进行其他更高级的优化。通过...
为了避免这一问题,有时候需要考虑使用迭代代替递归,或者使用尾递归优化等技术。同时,教师也应当鼓励学生去思考递归算法的时间复杂度和空间复杂度,使他们能够对递归有更全面的认识。 在实际应用中,递归函数常...
还有,书中的编译器优化部分可能教导读者如何利用编译器选项来优化代码,例如开启内联函数、使用尾递归等,以提升程序性能。 另外,错误处理和调试也是重要的主题。书中可能会讨论如何使用条件语句、异常处理和日志...
6. **尾递归优化**:一些编译器(如GCC)支持尾递归优化,即在递归调用作为函数的最后一个操作时,可以重用当前栈帧,从而避免栈空间的线性增长。但这不是所有C语言实现的默认行为,因此编写递归函数时应考虑到这...
本话题主要关注如何使用C语言实现一个功能,即“从尾到头”打印链表,这个过程可能会涉及到递归和链表的反转操作。 首先,我们要理解链表的基本结构。一个链表节点通常包含两部分:数据域,存储元素值;指针域,...
- 在递归过程中,可以使用尾递归优化,避免函数调用栈过深。 9. **代码实现**:C语言实现归并排序时,一般会包含以下步骤: - 分解:将数组分为左右两半,递归调用归并排序函数。 - 解决:对每个子数组进行排序...