递归-栈

学习:算法图解第三章-递归
在看算法图解的时候看到递归时讲到了与栈的关系,递归是靠栈来实现的,书上讲的原理都能看懂,实践下吧。结果一写代码就出现各种问题,在解决这些问题时也加深了递归与栈的关系。

现来看个简单的程序吧,计算数组N个数据的和,这里计算1-100的和,用递归来实现,贴上关键部分吧:

//int data[100] = {1, 2, 3, ...... 100};
//int len = 100;

int n = 0;
int sum(int data[], int len)
{
	if(n == len){		//基线条件
		return 0;
	}else{
		n++;
		return data[n-1] + sum(data, len);	//递归调用
	}
}

大家给下sum的结果?(自己先认真思考下结果)

其实这是我最开始写的代码(大神们一看肯定想,我操,还有这么2逼的写法,小学生都不如啊,没办法,本人能写出这么惊叹的代码,我自己都很佩服自己啊)

期待运行结果:5050(大家是不是也认为这个结果),VS上测试结果:10000,尼玛啥情况,与预期结果不符啊,程序有问题还是我理解的有问题,开始分析吧。
在设计N个数据和的本意是:
在这里插入图片描述
这里递归的条件是由n去控制的,计划实现的是data[0]+data[1]+…+data[99],那为什么结果是10000呢?前面说了,递归是栈来实现的,本来认为每进行一次递归调用时会把data[]首地址,n,len,以及data[n-1]的值都压人栈里,但是通过汇编代码发现实际上只有data[]首地址和len压人的栈中,n和data[n-1]并没有压人栈中,我们看下汇编代码。

	if(n == len){		//基线条件
009D14D5  mov         eax,dword ptr [n (9D7164h)] 
009D14DA  cmp         eax,dword ptr [len] 
009D14DD  jne         sum+35h (9D14E5h) 
		return 0;
009D14DF  xor         eax,eax 
009D14E1  jmp         sum+5Fh (9D150Fh) 
	}else{
009D14E3  jmp         sum+5Fh (9D150Fh) 
		n++;
009D14E5  mov         eax,dword ptr [n (9D7164h)] 
009D14EA  add         eax,1 
009D14ED  mov         dword ptr [n (9D7164h)],eax 
		return data[n-1] + sum(data, len);	//递归调用
009D14F2  mov         eax,dword ptr [len] 
009D14F5  push        eax  
009D14F6  mov         ecx,dword ptr [data] 
009D14F9  push        ecx  							//将n和data首地址压入栈中
009D14FA  call        sum (9D11FEh) 
009D14FF  add         esp,8 						//递归所有压栈完成后,下面开始出栈操作
009D1502  mov         edx,dword ptr [n (9D7164h)] 	//此刻出栈时,n为100
009D1508  mov         ecx,dword ptr [data] 
009D150B  add         eax,dword ptr [ecx+edx*4-4] 	//所以,每次出栈执行的都是+data[99]
	}
}

这里n是个全局变量,所以递归调用时n当前的值并不会压人栈中,并且data[n-1]的值也没有压人栈中,在递归所以压栈操作完成后进行出栈操作时,n为100,所以实际上每次执行的都是data[99]+sum(上一次的返回值)。所以最终是进行了100次data[99]的相加,所以运行结果是:10000而不是5050。

如果我们将程序稍微改下,结果会不一样:

//int data[100] = {1, 2, 3, ...... 100};
//int len = 100;

int n = 0;
int sum(int data[], int len)
{
	int local_n = 0;

	if(n == len){		//基线条件
		return 0;
	}else{
		n++;
		local_n = data[n-1];
		return local_n + sum(data, len);	//递归调用
		//return data[n-1] + sum(data, len);	//递归调用
	}
}

这样运行后运行结果是:5050,我们来分析下为什么,看看汇编代码:

	if(n == len){		//基线条件
002714D5  mov         eax,dword ptr [n (277164h)] 
002714DA  cmp         eax,dword ptr [len] 
002714DD  jne         sum+35h (2714E5h) 
		return 0;
002714DF  xor         eax,eax 
002714E1  jmp         sum+64h (271514h) 
	}else{
002714E3  jmp         sum+64h (271514h) 
		n++;
002714E5  mov         eax,dword ptr [n (277164h)] 
002714EA  add         eax,1 
002714ED  mov         dword ptr [n (277164h)],eax 
		local_n = data[n-1];
002714F2  mov         eax,dword ptr [n (277164h)] 
002714F7  mov         ecx,dword ptr [data] 
002714FA  mov         edx,dword ptr [ecx+eax*4-4] 	//将data[n-1]的值给edx
002714FE  mov         dword ptr [local_n],edx 		//将edx的值给local_n,local_n是局部变量,所有local_n的当前值会压人到栈中
		return local_n + sum(data, len);	//递归调用
00271501  mov         eax,dword ptr [len] 
00271504  push        eax  
00271505  mov         ecx,dword ptr [data] 
00271508  push        ecx  
00271509  call        sum (2711FEh) 
0027150E  add         esp,8 						//递归所有压栈完成后,下面开始出栈操作
00271511  add         eax,dword ptr [local_n] 		//每次出栈执行的都是+local_n(local_n的值为之前压人栈时的值)
	}
}        

这里local_n是个局部变量,所以递归压入栈时也会把local_n的当前的值压人到栈中,在递归所以压栈操作完成后进行出栈操作时执行的都是local_n(入栈时的值)+sum(上一次的返回值),所以实际上是1+2+3…+100,结果为5050。

我们来通过内存和寄存器的值来看看是不是这样的:
在这里插入图片描述
这里local_n分配的地址是0x004EF558,在ESP-EBP之间,最有local_n的值也会压人栈中。

至此我们学习了递归用栈实现方式以及递归中全局变量和局部变量的存储问题,在以后的设计中需注意其区别,避免出现不必要的bug。

后来又将程序又稍微改了下,两种方式,均可以得到预期结果:

//递归,求和 N个元素和
//int data[100] = {1, 2, 3, ...... 100};
//int len = 100;
--------------------------------------------------------------
//sum(data[0-98])+ data[99]  先算前面的和
int sum(int data[], int len)
{
	if(len == 0){		//基线条件
		return 0;
	}else{
		return data[len-1] + sum(data, len-1);	//递归调用
	}
}
-------------------------------------------------------------
//data[0] + sum(data[1-99])  先算后面的和
int  primary_len = 100;

int sum(int data[], int len)
{
	if(len == 0){		//基线条件
		return 0;
	}else{
		return data[primary_len-len] + sum(data, len-1);	//递归调用
	}
}

总结:本文研究了递归与栈的关系,以及全局变量与局部变量对栈的不同影响,从而深刻的认识了递归。

注:前面的测试环境都是在win7 vs2008上进行的,前面结果是10000的同样代码在linux下测试结果则为5050,由此linux与windows在某些处理上可能稍有不同,带后面进行linux研究后在进行总结!
本人能力与理解力有限,如有有理解错的希望大家一起交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值