那么,我们就直接进入正题啦
何为递归?
先来一段递归版生僻字:
计算机中的递归
能够反复的调用自身啊~
是一种高效的方式~
咳咳咳,编不下去了
简单来说:
程序调用自身的编程技巧称为递归( recursion)。
而递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
那么,为什么说对于递归不要“贪杯”呢?
由于递归的反复调用自身,会进入一个“循环”,而这个循环在缓冲区的存储方式是以栈来实现的,但是栈在缓冲区中一般会被分配8MB的空间,递归的深度过于深会使栈溢出从而出现“段错误”的指令。
如图:(为栈和堆的分配方式)
stack为栈,栈从高地址往低地址方向增长,且一般为8MB,而heap为堆,朝相反方向增长。
栈和堆里的内容时刻都在变化,随着程序的运行,这一块区域会被不断地释放和申请,但当这块区域被使用“过度”时,栈就溢出了,会返回“段错误(核心已转储)”的字样,具体的详情请看其他博客,这里简单地提一下。
接下来,我们来看一段代码:
/* Example of deep recursion */
#include <stdio.h>
#include <stdlib.h>
int recurse(int x) {
int a[1<<15]; /* 4 * 2^15 = 64 KiB */
printf("x = %d. a at %p\n", x, a);
a[0] = (1<<14)-1;
a[a[0]] = x-1;
if (a[a[0]] == 0)
return -1;
return recurse(a[a[0]]) - 1;
}
int main(int argc, char *argv[]) {
int x = 100;
if (argc > 1)
x = atoi(argv[1]);
int v = recurse(x);
printf("x = %d. recurse(x) = %d\n", x, v);
return 0;
}
这个代码是深度递归(deep recursion),就是不断的在栈里申请空间,相当于不断减少%rsp(栈指针)的值,直到触碰8MB的限制,最后导致了栈的溢出,以及得到返回值“段错误(核心已转储)”。
现在给大家看看在ubantu上的运行结果:
chunshuai@chunshuai-virtual-machine:~/桌面/testcode$ ./runaway
x = 100. a at 0x7ffc21a6d900
x = 99. a at 0x7ffc21a4d8d0
x = 98. a at 0x7ffc21a2d8a0
x = 97. a at 0x7ffc21a0d870
x = 96. a at 0x7ffc219ed840
x = 95. a at 0x7ffc219cd810
x = 94. a at 0x7ffc219ad7e0
x = 93. a at 0x7ffc2198d7b0
x = 92. a at 0x7ffc2196d780
x = 91. a at 0x7ffc2194d750
x = 90. a at 0x7ffc2192d720
x = 89. a at 0x7ffc2190d6f0
x = 88. a at 0x7ffc218ed6c0
x = 87. a at 0x7ffc218cd690
x = 86. a at 0x7ffc218ad660
x = 85. a at 0x7ffc2188d630
x = 84. a at 0x7ffc2186d600
x = 83. a at 0x7ffc2184d5d0
x = 82. a at 0x7ffc2182d5a0
x = 81. a at 0x7ffc2180d570
x = 80. a at 0x7ffc217ed540
x = 79. a at 0x7ffc217cd510
x = 78. a at 0x7ffc217ad4e0
x = 77. a at 0x7ffc2178d4b0
x = 76. a at 0x7ffc2176d480
x = 75. a at 0x7ffc2174d450
x = 74. a at 0x7ffc2172d420
x = 73. a at 0x7ffc2170d3f0
x = 72. a at 0x7ffc216ed3c0
x = 71. a at 0x7ffc216cd390
x = 70. a at 0x7ffc216ad360
x = 69. a at 0x7ffc2168d330
x = 68. a at 0x7ffc2166d300
x = 67. a at 0x7ffc2164d2d0
x = 66. a at 0x7ffc2162d2a0
x = 65. a at 0x7ffc2160d270
x = 64. a at 0x7ffc215ed240
x = 63. a at 0x7ffc215cd210
x = 62. a at 0x7ffc215ad1e0
x = 61. a at 0x7ffc2158d1b0
x = 60. a at 0x7ffc2156d180
x = 59. a at 0x7ffc2154d150
x = 58. a at 0x7ffc2152d120
x = 57. a at 0x7ffc2150d0f0
x = 56. a at 0x7ffc214ed0c0
x = 55. a at 0x7ffc214cd090
x = 54. a at 0x7ffc214ad060
x = 53. a at 0x7ffc2148d030
x = 52. a at 0x7ffc2146d000
x = 51. a at 0x7ffc2144cfd0
x = 50. a at 0x7ffc2142cfa0
x = 49. a at 0x7ffc2140cf70
x = 48. a at 0x7ffc213ecf40
x = 47. a at 0x7ffc213ccf10
x = 46. a at 0x7ffc213acee0
x = 45. a at 0x7ffc2138ceb0
x = 44. a at 0x7ffc2136ce80
x = 43. a at 0x7ffc2134ce50
x = 42. a at 0x7ffc2132ce20
x = 41. a at 0x7ffc2130cdf0
x = 40. a at 0x7ffc212ecdc0
x = 39. a at 0x7ffc212ccd90
x = 38. a at 0x7ffc212acd60
段错误 (核心已转储)
x最多取至38,之后栈已溢出
现在来看看带参20的运行结果:
chunshuai@chunshuai-virtual-machine:~/桌面/testcode$ ./runaway 20
x = 20. a at 0x7ffc876555f0
x = 19. a at 0x7ffc876355c0
x = 18. a at 0x7ffc87615590
x = 17. a at 0x7ffc875f5560
x = 16. a at 0x7ffc875d5530
x = 15. a at 0x7ffc875b5500
x = 14. a at 0x7ffc875954d0
x = 13. a at 0x7ffc875754a0
x = 12. a at 0x7ffc87555470
x = 11. a at 0x7ffc87535440
x = 10. a at 0x7ffc87515410
x = 9. a at 0x7ffc874f53e0
x = 8. a at 0x7ffc874d53b0
x = 7. a at 0x7ffc874b5380
x = 6. a at 0x7ffc87495350
x = 5. a at 0x7ffc87475320
x = 4. a at 0x7ffc874552f0
x = 3. a at 0x7ffc874352c0
x = 2. a at 0x7ffc87415290
x = 1. a at 0x7ffc873f5260
x = 20. recurse(x) = -20
呐呐呐,栈没有溢出,完美运行噢~
最后来个总结。尽管程序的递归能够减少代码量,但在使用递归的过程中记得给递归一个出口,以及注意递归不能过于深度地进行,否则将占用大量缓冲区,这对运行效率来说还是会大打折扣的,记住噢!!! 递归虽好,但不要“贪杯”,小心栈溢出!!!