网站制作需要多少钱?,常用的网络营销工具有哪些,wordpress 如何修改主题宽度,网站建设支付C语言中递归什么时候能够省略return引发的思考#xff1a;通过内联汇编解读C语言函数return的本质 事情的经过是这种#xff0c;博主在用C写一个简单的业务时使用递归#xff0c;因为粗心而忘了写return。结果发现返回的结果依旧是正确的。经过半小时的反汇编调试。证明了我… C语言中递归什么时候能够省略return引发的思考通过内联汇编解读C语言函数return的本质 事情的经过是这种博主在用C写一个简单的业务时使用递归因为粗心而忘了写return。结果发现返回的结果依旧是正确的。经过半小时的反汇编调试。证明了我的猜想如今在博客里分享。也是对C语言编译原理的一次加深理解。引子首先我想以一道题目引例比較能体现出问题。例1
#include stdio.h
/**函数功能:用递归实现位运算加法*/
int Add_Recursion(int a,int b)
{int carry_num 0, add_num 0;if (b 0){return a;}else{add_num a^b;carry_num (ab)1;Add_Recursion(add_num, carry_num);}
}
int main()
{int num Add_Recursion(1, 1);printf(%d\n,num);getchar();
} 问题是。运行如上的程序打印出来的数值是多少大家可能会觉得这个非常的弱智即使作为小公司的笔试题来说都登不上大雅之堂。 ——————————–图1 例题1的运行结果———————答案是2毫无疑问仅仅是一个简单的递归而已。 可是假设我把题目改一下例2
#include stdio.h
int changestack()
{return 3;
}
/**函数功能:用递归实现位运算加法*/int Add_Recursion(int a,int b)
{int carry_num 0, add_num 0;if (b 0){return a;}else{add_num a^b;carry_num (ab)1;Add_Recursion(add_num, carry_num);changestack();}
}int main()
{int num Add_Recursion(1, 1);printf(%d\n,num);getchar();
} 大家看看上边的程序。运行结果会是多少 可能有非常多朋友细心已经发现了猫腻。 可能也有部分朋友会有些困惑这个程序仅仅是在递归的实现函数后中加了一个无关紧要的函数调用为什么会影响函数返回的结果呢。 其实printf打印出来的结果不对。运行结果是3 —————————-图2 例题2的运行结果————————-为什么会出现这个问题呢。实际上正常情况下的递归。在else语句里进行递归调用时。应当加上return。因为return的缺失导致了函数返回值被changestack()函数篡改。从而在main函数中读到了错误的返回值。else{add_num a^b;carry_num (ab)1;return Add_Recursion(add_num, carry_num);changestack();}假设将上文的代码改正如上那不会出现不论什么问题。当然不会出错此时有了returnreturn后边的changestack根本就不会有不论什么机会运行 如今来一步一步来分析发生错误的本质。 ——————–图三 例二函数的递归分析—————————我们分析上边代码的运行过程。首先在main函数中调用Add_Recursion(1,1),本意就是计算11的值而且将函数返回值传递给printf打印出来。 在递归调用Add_Recursion函数(简称add)计算11时前两次递归调用因为不满足递归出口条件进位加数carry_num为0。会跳入else分支进行递归调用。直到第三次递归调用时因为carry_num为0。这时返回了累加结果。问题是仅仅有第三次的add递归调用进行了return第一次和第二次在函数返回时都没有return而是在返回子层次递归后调用changestack()函数后返回调用自己的函数层级。在第一层递归调用返回给main的时候,add_recursion并没有return而是在运行完changestack直接返回main函数而此时main函数的printf在解析返回值时实际上错误的解析了changestack的返回值。因此才出现113的错误综上分析发生这一切的原因就是 函数运行结束返回时。会将返回值压栈(理论上如此实际上编译器会优化将返回值给eax寄存器过渡。VC就是使用的eax临时保存)。VC编译器解析函数返回值(整型)时直接将eax的值读出当做返回值。 ———————-图四 反汇编分析VC编译器对return的处理———-依据反汇编分析能够看到VC编译器对changestack()中的return 3汇编的结果也就是 mov eax,3。实际上就是把返回值赋予eax由eax寄存器过渡给此函数的调用函数使用。我们在下图中能够看到main函数中将changestack()的返回值给num赋值的详细过程也就是将eax的值返回给num的所在的内存地址。 ——————————图五 函数返回值的“弹栈”细则——————————-这样一切就有了解释。——————-图六 例题一为什么会碰巧正确的递归分析————— 尽管第一题的结果尽管正确printf在读取Add_Recursion返回值时。读取的不是第一次递归调用的结果而是第三次递归调用return b的结果(第三次递归返回时暂存在eax寄存器中)。而在之后的递归返回中凑巧eax都没有被改变。因此这样使用递归尽管没有在须要return的地方return是能够得到正确结果。 实际上我们能够用一条内联汇编代码验证我们的猜想是否正确。我们在递归调用的后边使用内联汇编加上一条汇编代码改变eax的值。 ——————————-图七 用内联汇编解读C语言的return本质—————————– 我们在递归函数Add_Recursion的后边加了一条汇编代码让函数结束时改变eax的值。能够看到。主函数中将函数返回值误觉得了我们在汇编语言中设定的3.打印出了113这种谬论。实际上我们在编译例题中的程序在编译时C编译器会提出警告 warning C4715: “Add_Recursion”: 不是全部的控件路径都返回值 有返回值的函数不是全部的支路都会进行返回值假设大家把博客中的程序在更加严格的C编译器上编译会报错。这仅仅是一个非常easy的案例。或许我们会运气好实现函数的功能可是在进行复杂情况的树状甚至图状递归中假设不确定自己是否一定能得到终于结果请务必将每一种情况都return返回值这样来避免程序意外出错。C语言的灵活性应该给我们造福而不应该给我们的程序提供不稳定的因素。 posted on 2017-08-07 18:01 mthoutai 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/mthoutai/p/7300489.html