C#开发笔记概述

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/962 访问。

从A函数跳转到B函数,在B函数执行完毕后,程序为什么能精确的返回到A函数中未执行完的代码区域?

首先,我们要知道什么是栈和栈帧。

栈是一种特殊的线性表,仅能在线性表的一端-栈顶进行操作,栈底不允许操作。

栈的特性:后进先出(Last In First Out or LIFO)

栈帧是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等。

为了我们不会因为文字的描述而产生歧义,我们约定程序从左向右运行,左边为前,右边为后。

从函数A跳转到函数B时,将函数的执行环境压入栈中,显然栈帧就是栈中所需要存放的数据。函数B执行完成后,运行环境知道,刚执行完成了一个函数,现在要回到某个地方了,那么回到哪里呢?当然是从栈中弹出最近一个栈帧(如果有的话)并取出相关信息即可。

这堪称是一个完美的设计!这种设计为函数式编程带来了高可靠性的同时也带来了性能损失。事实上运行环境维护这样的数据结构并不轻松,首先我们需要一个特殊的数据区域来存放这个栈,这个栈通常被称为“调用堆栈”(Call Stack),并且通常有 1M 的空间限制(注意这个值是可以改的)和  次的数量限制(32位系统,一般情况下 1M 的空间限制首先到达)。即每次从一个函数跳转到另外一个函数会使栈增加一个计数并存放栈帧信息,下次程序执行路径在函数终点处需要返回时又要从栈中取出栈顶信息(如果有的话)。但情况并非总是如此!

一般情况下总是需要这样的一个栈帧,但如果函数A跳转到函数B时,该处已经是函数A的最后一句是又会怎么样呢?显然,这个栈帧不是必须的,因为返回此处时,由于函数A也即将结束,又会往前返回到上一个栈帧,那何不直接返回到上一个栈帧呢?事实上现代编译器都会为这种情况做出优化,运行时不会为其增加栈帧,而是在函数B运行完成后,直接返回至函数A之前所存放的栈帧信息(如果有的话)。显然递归属于特殊函数的跳转,它跳转到其本身。

再来看看什么是尾递归。

如果一个函数中所有递归形式的调用都出现在函数的末尾(逻辑上的末尾或者说代码路径的末尾),我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

我们通过1个案例来具体的分析一下,之后再回头看上面的描述可能会更加清楚的了解运行环境是如何追踪函数的运行状态的。

public class Program {

    public static void Main(string[] args) {
Add(100);
Add(95);
Add(80);
Console.ReadKey();
} private static void Add(int score) {
#line 100
if(score == 100) {
Perfect();
}
//请注意这里是另起if
#line 200
if(score >= 90) {
Excellent();
}
#line 300
else {
ComeOn();
}
} private static void Perfect() {
Console.WriteLine("Perfect!");
} private static void Excellent() {
Console.WriteLine("Excellent!");
} private static void ComeOn() {
Console.WriteLine("ComeOn!");
} }

我们先来分析 Add(100) 的执行,Main->Add->Perfect,每次跳转函数都会增加一个栈帧,以便程序执行可以沿着 Perfect->Add->Main 这样的路径往前返回。在这个过程中显然每次函数的跳转都会增加栈帧。

如果你已经明白了 Add(100) 的执行过程,那么现在要分析的 Add(90) 的执行过程可能不会太难。但需要注意到的一点是,当分数为90的时候,第100行的代码(#line 100)被执行到的时候,运行环境已经知道这是最后一行可以被执行到的代码,因为 if else 中只有一个可以被命中的路径,所以程序往前返回时的路径是这样的 Excellent->Add->Main (红色删除部分表示未被返回) 。因为 Add(90) 跳转到 Excellent 方法时,没有为其增加栈帧,因为完全没有必要。显然 Add(80) 也是这样的。

接一下,我们再看看函数有返回值时,会出现什么情况。

private static void Add(int score) {
string description = string.Empty;
#line 100
if(score == 100) {
description = Perfect();
}
//请注意这里是另起if
#line 200
if(score >= 90) {
description = Excellent();
}
#line 300
else {
description = ComeOn();
}
} private static string Perfect() {
return "Perfect!";
} private static string Excellent() {
return "Excellent!";
} private static string ComeOn() {
return "ComeOn!";
}

在分析这种情况前,我们再来回顾一下尾递归的概念:

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/962 访问。

如果一个函数中所有递归形式的调用都出现在函数的末尾(逻辑上的末尾或者说代码路径的末尾),我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

显然,当需要这个函数的返回值时,其不属于尾递归。

C#开发笔记之06-为什么要尽可能的使用尾递归,编译器会为它做优化吗?的更多相关文章

  1. C#开发笔记,点点细微,处处真情,记录开发中的难言之隐

    该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/956 访问. 概述 本系列文章将会向大家介绍本人实际开发过程中所遇到技术 ...

  2. 驱动开发学习笔记. 0.06 嵌入式linux视频开发之预备知识

    驱动开发读书笔记. 0.06  嵌入式linux视频开发之预备知识 由于毕业设计选择了嵌入式linux视频开发相关的项目,于是找了相关的资料,下面是一下预备知识 UVC : UVC,全称为:USB v ...

  3. TERSUS无代码开发(笔记06)-简单实例手机端页面设计

    手机端的设计 1.页面说明 2.默认页面===>提交请假单(上面页面双击进入,页面主要编辑区) 2.1默认页面===>提交请假单===>头部区(页面部份主要编辑区01) 2.1.1默 ...

  4. cocos2dx3.0 超级马里奥开发笔记(两)——正确的规划游戏逻辑

    我将不得不拿出一个完整的开发笔记.由于个人原因.代码已OK该,博客,那么就不要粘贴代码,直接解释了整个游戏设计,更确切地说,当新手应该注意的地方发展. 1.继承类和扩展作用的权----展阅读(MVC) ...

  5. iOS回顾笔记(06) -- AutoLayout从入门到精通

    iOS回顾笔记(06) -- AutoLayout从入门到精通 随着iOS设备屏幕尺寸的增多,当下无论是纯代码开发还是Xib/StoryBoard开发,自动布局已经是必备的开发技能了. 我使用自动布局 ...

  6. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

  7. 安卓开发笔记——打造万能适配器(Adapter)

    为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需 ...

  8. 【转】Android开发笔记——圆角和边框们

    原文地址:http://blog.xianqu.org/2012/04/android-borders-and-radius-corners/ Android开发笔记——圆角和边框们 在做Androi ...

  9. Java开发笔记(一百零一)通过加解锁避免资源冲突

    前面介绍了如何通过线程同步来避免多线程并发的资源冲突问题,然而添加synchronized的方式只在简单场合够用,在一些高级场合就暴露出它的局限性,包括但不限于下列几点:1.synchronized必 ...

随机推荐

  1. Ethical Hacking - NETWORK PENETRATION TESTING(5)

    Deauthentication Attacks Theory This attack is used to disconnect any device from any network within ...

  2. 手把手教你安装Office 2019 for Mac ,安装包和破解码都给你准备好了,还装不上的话,你找我!

    准备一个安装包,和一个破解工具 ​ 安装MicrosoftOffice16.23.19030902_Installer.pkg, 注意在断网情况下安装 同时不要自动更新 , 安装好之后不要打开文件!​ ...

  3. 在ASP.NET中,<%= %>和<%# %>有什么区别

    asp.net中<%#%>出现在repeater gridview等控件中.用以绑定控件的datasource asp.net中<%%>的意思是 上运行c#或者vb代码,比如: ...

  4. 题解 SP687 【REPEATS - Repeats】

    考虑可以枚举字符串上的两个点,求出两个点所对应后缀的\(LCP\)和所对应前缀的\(LCS\),两点之间的距离为\(len\),则这两个点对答案的贡献为: \[ \frac{LCS+LCP+L-1}{ ...

  5. DJANGO-天天生鲜项目从0到1-013-订单-支付宝支付

    本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习 https://www.bilibili.com/video/BV1vt41147K8?p= ...

  6. 深度搜索---------Lake counting

    #include<iostream>#include<cstdio>#include<cstdlib>#define maxn 100char ch[maxn][m ...

  7. JVM系列之:对象的锁状态和同步

    目录 简介 java对象头 java中锁状态的变化 偏向锁biased locking 轻量级锁thin lock 重量级锁 三种锁状态的不同 简介 锁和同步是java多线程编程中非常常见的使用场景. ...

  8. eclipse IDE usage of my own and tutorials link list

    设置 字符集 Eclipse 修改字符集 默认情况下 Eclipse 字符集为 GBK,但现在很多项目采用的是 UTF-8,这是我们就需要设置我们的 Eclipse 开发环境字符集为 UTF-8, 设 ...

  9. Google免费新书-《构建安全&可靠的系统》

    前段时间riusksk在公众号分享的Google安全团队的新书,好书,全英原版,开源免费. 免费下载地址:https://static.googleusercontent.com/media/land ...

  10. Django序列化组件Serializers详解

    本文主要系统性的讲解django rest framwork 序列化组件的使用,基本看完可以解决工作中序列化90%的问题,写作参考官方文档https://www.django-rest-framewo ...