函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?

86 views
Skip to first unread message

石奇偲

unread,
Feb 11, 2011, 8:51:15 PM2/11/11
to pon...@googlegroups.com
这是看sicp,然后看别人的读书笔记,想到的一个问题.
有人说写一个无限递归的程序 如果是函数式语言就不会出现栈溢出,而是无限循环,如果是命令式语言就会抛出栈溢出的异常.

但是我想
1.如果这个无限递归不是尾递归的形式写出来的,在函数式语言里面,递归的时候就不要保留现场到栈空间了?
然后在DrScheme里随便敲了一个
(define (f1.11a count)
  (cond( (< count 4) count)
       ( else (+ (f1.11a (- count 1)) (+ (* 2 (f1.11a (- count 2))) (* 3 (f1.11a (- count 3))))))))
(f1.11a 512)
程序跑了很长的时间,最后提示内存用尽,而不是栈溢出!

2.基于以上实验,我想 一般来说编译器会把递归的现场保存在操作系统提供的栈空间里,但是一般操作系统给每个进程提供的栈空间有限,超过了,程序就抛出异常.现代编译器是不是把递归现场保存在堆里(这里的堆不是数据结构里的堆,是相对于栈空间来说的另外的内存空间)堆的大小一般没有限制,所以不会出现栈溢出.
然后我用c#写了一段:
using System;
using System.Collections.Generic;
using System.Text;

namespace testStack
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine(Digui(200));
        }

        static int Digui(int i)
        {
            if (i == 0) return 0;
            else return Digui(i--) + 1;
        }

    }
}
运行一瞬间就报栈溢出了.

显然1不能用DrScheme使用了类似动态规划的表格技术来优化递归,那么怎么解释程序1?
函数调用的现场不能保存在堆里面么?如果是,为什么?如果不是,怎么解释程序2?

Jeffrey Zhao

unread,
Feb 11, 2011, 9:04:48 PM2/11/11
to pon...@googlegroups.com
这问题说简单简单,说复杂也复杂。代码里的尾递归可以被编译器和执行环境优化成循环,或者“尾调用”,此时不会栈溢出。同样,编译器或执行环境也可能不优化代码里的尾递归,于是就会溢出。
 
你这里的C#代码它本身就不是尾递归,执行时候就要不断保存占空间了。至于DrScheme,谁知道它是怎么执行的呢。如果某人写个Scheme解释器,函数调用时不用栈,所有数据往堆上搞,自然不会栈溢出,但是会内存用尽了。
 
如果真做了尾递归优化的,如果不保留栈,函数里也不开辟新空间等等,结果就是死循环,不会占用内存。
 

yuan zhu

unread,
Feb 11, 2011, 9:06:51 PM2/11/11
to pon...@googlegroups.com
请google : 尾递归优化

2011/2/12 石奇偲 <shiq...@gmail.com>

石奇偲

unread,
Feb 11, 2011, 9:26:33 PM2/11/11
to pon...@googlegroups.com
我不是在讨论什么是尾递归,
而且我觉的

(define (f1.11a count)
  (cond( (< count 4) count)
       ( else (+ (f1.11a (- count 1)) (+ (* 2 (f1.11a (- count 2))) (* 3 (f1.11a (- count 3))))))))
(f1.11a 512)
不是尾递归的吧?

我写了一个我以为是尾递归的版本
(define (f1.11b a b c count)
  (if(= count 3);小于3的视为无意义
     a
     (f1.11b (+ a (+ (* 2 b) (* 3 c))) a b (- count 1))))
(f1.11b 512)
这个版本的执行速度比前者快很多.
而且如果第一个版本编译器优化成尾递归了,就是用迭代来执行,应该不会占用那么多的内存吧?

Jeffrey Zhao

unread,
Feb 11, 2011, 9:32:38 PM2/11/11
to pon...@googlegroups.com
我是说你如果要“不保留栈”那么需要尾递归,第一个当然不是尾递归。正是由于第一个不是尾递归,所以内存还是会不足。优化后的尾递归就是相当于个循环罢了。

石奇偲

unread,
Feb 11, 2011, 9:39:12 PM2/11/11
to pon...@googlegroups.com
这就是我的问题,第一个Scheme的例子和第二个c#的例子,我都特意写了不是尾递归的版本,所以不是应该按照普通的函数调用来执行么?

那么Scheme的例子,每次运行递归调用都会保留现场,如果是保留到栈里,为什么会是在执行了几分钟之后报内存耗尽,而不是像c#的例子在运行之后一瞬间报栈溢出?

石奇偲

unread,
Feb 11, 2011, 9:45:56 PM2/11/11
to pon...@googlegroups.com
恩,这也是我的看法.但是为什么这么显然的优化,c#不做?

Jeffrey Zhao

unread,
Feb 11, 2011, 9:45:56 PM2/11/11
to pon...@googlegroups.com
这个Scheme在执行的时候,你确定每次函数调用使用的就是像C#里的call指令?
 
我一开始就说了,我完全可以写一个Scheme解释器,把栈里的信息放到堆上去,这样就不是栈溢出,而是内存不足。

Jeffrey Zhao

unread,
Feb 11, 2011, 9:48:33 PM2/11/11
to pon...@googlegroups.com
听不懂了,你没写成尾递归,C#怎么做优化?你的Scheme版本也没有优化啊,还是会内存不足。
 
From: 石奇偲
Sent: Saturday, February 12, 2011 10:45 AM
Subject: Re: [TL] 函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?
 

石奇偲

unread,
Feb 11, 2011, 9:51:52 PM2/11/11
to pon...@googlegroups.com
按照"如果某人写个Scheme解释器,
函数调用时不用栈,所有数据往堆上搞,自然不会栈溢出"的思路,
c#的编译器为什么不把函数调用的现场保存在堆里?
至少在栈快溢出的时候把现场的保存转到堆里,这样的话如果我写的不是一个无限的递归而是一个比较深的递归,可能可以跑出结果,而不是在程序刚一运行的时候就报栈空间溢出?

qiaojie

unread,
Feb 11, 2011, 9:56:04 PM2/11/11
to pon...@googlegroups.com
真把函数调用的现场保存在堆里你又要抱怨C#性能太差了


 
2011/2/12 石奇偲 <shiq...@gmail.com>

石奇偲

unread,
Feb 11, 2011, 9:57:10 PM2/11/11
to pon...@googlegroups.com
就像第一个Scheme的程序,如果调用的是(f1.11a 28)而不是(f1.11a 512),则在4,5秒之后就出了结果,而c#的程序,即使是调用 System.Console.WriteLine(
Digui(28));也会报栈溢出

sagasw

unread,
Feb 11, 2011, 9:56:53 PM2/11/11
to pon...@googlegroups.com
C#应该是有一个dump机制的,
如果这玩意做成内置的,那得确定每个程序员都想这样做

------------------------------------------
blog: http://sunxiunan.com/
C++, Lua, living in Dalian
http://twitter.com/sagasw
------------------------------------------


2011/2/12 石奇偲 <shiq...@gmail.com>

Jeffrey Zhao

unread,
Feb 11, 2011, 9:58:39 PM2/11/11
to pon...@googlegroups.com
直接用call指令速度快啊,所以你一下子就看到栈溢出了。平时有多少可能栈溢出的情况,你真知道可能递归比较深,自己写成尾递归形式么。

Jeffrey Zhao

unread,
Feb 11, 2011, 10:00:25 PM2/11/11
to pon...@googlegroups.com
刚才没看你代码,现在你这么一说我去看了看,怎么28层递归就会栈溢出?200层也不该溢出。吃完饭试验一下。

Jeffrey Zhao

unread,
Feb 11, 2011, 10:01:49 PM2/11/11
to pon...@googlegroups.com
我擦,你自己看看你写的代码:
 
static int Digui(int i)
{
if (i == 0) return 0;
else return Digui(i--) + 1;
}
 
Digui(i--)不还是Digui(i)么,你放个1进去它也栈溢出。

sagasw

unread,
Feb 11, 2011, 10:01:59 PM2/11/11
to pon...@googlegroups.com
老兄还是没太搞明白Jeff想说的。
首先如果要比较,那得确定Scheme也是用的栈来保持调用数据。
其次,一般来讲程序栈都是有限的,大概也就是几M左右,当然很快溢出。


------------------------------------------
blog: http://sunxiunan.com/
C++, Lua, living in Dalian
http://twitter.com/sagasw
------------------------------------------


2011/2/12 石奇偲 <shiq...@gmail.com>
就像第一个Scheme的程序,如果调用的是(f1.11a 28)而不是(f1.11a 512),则在4,5秒之后就出了结果,而c#的程序,即使是调用 System.Console.WriteLine(

石奇偲

unread,
Feb 11, 2011, 10:04:36 PM2/11/11
to pon...@googlegroups.com
哦,问题出在这里!
改成else return Digui(--i) + 1;就行了.而且运行的速度还很快.
看来c#也是把现场放到堆里了?
我把Scheme那个程序改写成c#版本的试一试不知道会怎么样?

up duan

unread,
Feb 11, 2011, 10:06:10 PM2/11/11
to pon...@googlegroups.com


2011/2/12 石奇偲 <shiq...@gmail.com>
这就是我的问题,第一个Scheme的例子和第二个c#的例子,我都特意写了不是尾递归的版本,所以不是应该按照普通的函数调用来执行么?

那么Scheme的例子,每次运行递归调用都会保留现场,如果是保留到栈里,为什么会是在执行了几分钟之后报内存耗尽,而不是像c#的例子在运行之后一瞬间报栈溢出?

Scheme的流程控制结构是基于Continuation的。Continuation不采用FILO的stack,而是采用heap。

sagasw

unread,
Feb 11, 2011, 10:09:14 PM2/11/11
to pon...@googlegroups.com
发现对这个问题感兴趣的还不少,
我有一篇不科学没结论的博客有点近似
http://sunxiunan.com/?p=1784


------------------------------------------
blog: http://sunxiunan.com/
C++, Lua, living in Dalian
http://twitter.com/sagasw
------------------------------------------


2011/2/12 石奇偲 <shiq...@gmail.com>
哦,问题出在这里!

Jeffrey Zhao

unread,
Feb 11, 2011, 10:12:45 PM2/11/11
to pon...@googlegroups.com
为什么运行速度快你就觉得在堆里了?就是普通的call啊call的,一切都在栈上。
 
这里你别i++或是--i,就用i - 1啊,无论语义还是为了不容易出错的情况下,都应该用i-1,不是么。

Larry, LIU Xinyu

unread,
Feb 11, 2011, 10:19:07 PM2/11/11
to TopLanguage
Hi,

抛开尾递归不说。

其实你耐心的继续看SICP,看到求值器的环境模型那一章就明白了,

FP语言的模型和procedural语言的模型是不同的,后者assume用户(也就是程序员)不会大量使用递归。故而使用栈来简化编译器实现。
我记得当年在DOS上使用Turbo Pascal时,语言的手册里明确标明,递归的使用不得超过7层,否则会发生栈溢出。

我再给一个类比问题,很经典。相信你一看就明白了。

这就是深度优先搜索迷宫问题,我们知道深度优先的最简单实现是递归实现。并且这个递归不是尾递归。
在DOS时代,迷宫的深度造成可以轻易大于7,故而递归一定会溢出,那时候竞赛遇到这类题目我们的典型解法就是使用stack。
每次搜索扩展时把当前走法压栈,然后回溯的时候弹出。

还有一个名题是八皇后,8正好比7大一,所以如果使用DFS解八皇后,就必须使用自定义的stack。

最后我再强调一点,许多多范式语言最近也争先恐后的加入了FP的一些库,这也算是感时髦吧。但是他们背后的计算模型是procedural的。
所以你可以轻易判断这些FP工具是否可以支持你来实现工业级别的应用,这个方法就是实现一个尾递归的阶乘:
例如python:
# I tested that python doesn't support tail recursion.
# as below:
# def fact(n):
# if(n==0):
# return 1
# else:
# return n*fact(n-1)
# if we call fact(1000), the error is:
# RuntimeError: maximum recursion depth exceeded

虽然python里有map, reduce, filter等等看似functional的东西,但是它用的不是FP模型。

--
LIU
https://sites.google.com/site/algoxy/home

On Feb 12, 10:51 am, 石奇偲 <shiqi...@gmail.com> wrote:
> 按照"如果某人写个Scheme解释器,
> 函数调用时不用栈,所有数据往堆上搞,自然不会栈溢出"的思路,
> c#的编译器为什么不把函数调用的现场保存在堆里?
> 至少在栈快溢出的时候把现场的保存转到堆里,这样的话如果我写的不是一个无限的递归而是一个比较深的递归,可能可以跑出结果,而不是在程序刚一运行的时候就报栈空间溢出?
>
> 在 2011年2月12日 上午10:48,Jeffrey Zhao <je...@live.com>写道:
>
> > 听不懂了,你没写成尾递归,C#怎么做优化?你的Scheme版本也没有优化啊,还是会内存不足。
>
> > Jeffrey Zhao
> > Blog:http://blog.zhaojie.me/
> > Twitter:http://twitter.com/jeffz_cn
>

> > *From:* 石奇偲 <shiqi...@gmail.com>
> > *Sent:* Saturday, February 12, 2011 10:45 AM
> > *To:* pon...@googlegroups.com
> > *Subject:* Re: [TL] 函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?

石奇偲

unread,
Feb 11, 2011, 10:20:39 PM2/11/11
to pon...@googlegroups.com
恩,根据"Scheme的流程控制结构是基于Continuation的。
Continuation不采用FILO的stack,而是采用heap。"
然后搜了一些  关于Continuation的文章,
明白你们的意思了.

Scheme里函数调用用的是Continuation方法,有点类似动态规划,而不是用栈.
c#里面用的是栈

石奇偲

unread,
Feb 11, 2011, 10:26:20 PM2/11/11
to pon...@googlegroups.com
恩,明白了.谢谢!

Jeffrey Zhao

unread,
Feb 11, 2011, 10:40:50 PM2/11/11
to pon...@googlegroups.com
def fact(n):
if(n==0):
return 1
else:
return n*fact(n-1)

��Ȼ��ȷPython��һ�����β�ݹ��Ż��������������ȷ����β�ݹ顣

��������Ϊ������ģ����һ�֣�ִ�л�������һ�֡�Ϊ�˱�֤���ܵ����أ�û�취˭�÷�ŵ������ǹ��ʽ�ģ���FP����Ҳ���п�������ջ�ġ����Ի���Ҫ�����ס��ض����ԡ��Ĵ��?ʽ���ú���ʽ�����ʽ���л�������̫���ˡ���

-----ԭʼ�ʼ�-----
From: Larry, LIU Xinyu
Sent: Saturday, February 12, 2011 11:19 AM
To: TopLanguage
Subject: [TL] Re: ������õ�ʱ��,�ֳ�һ��Ҫ�����ڲ���ϵͳ�ṩ��ջ�ռ�����ô?

Hi,

�׿�β�ݹ鲻˵��

��ʵ�����ĵļ���SICP��������ֵ���Ļ���ģ����һ�¾������ˣ�

FP���Ե�ģ�ͺ�procedural���Ե�ģ���Dz�ͬ�ģ�����assume�û���Ҳ���dz���Ա���������ʹ�õݹ顣�ʶ�ʹ��ջ���򻯱�����ʵ�֡�
�Ҽǵõ�����DOS��ʹ��Turbo Pascalʱ�����Ե��ֲ�����ȷ�������ݹ��ʹ�ò��ó���7�㣬����ᷢ��ջ�����

���ٸ�һ��������⣬�ܾ��䡣������һ���������ˡ�

�����������������Թ����⣬����֪��������ȵ����ʵ���ǵݹ�ʵ�֡���������ݹ鲻��β�ݹ顣
��DOSʱ���Թ��������ɿ������״���7���ʶ�ݹ�һ�����������ʱ��������������Ŀ���ǵĵ��ͽⷨ����ʹ��stack��
ÿ��������չʱ�ѵ�ǰ�߷�ѹջ��Ȼ����ݵ�ʱ�򵯳���

����һ�������ǰ˻ʺ�8��ñ�7��һ���������ʹ��DFS��˻ʺ󣬾ͱ���ʹ���Զ����stack��

�������ǿ��һ�㣬���෶ʽ�������Ҳ���ȿֺ�ļ�����FP��һЩ�⣬��Ҳ���Ǹ�ʱ�ְɡ��������DZ���ļ���ģ����procedural�ġ�
��������������ж���ЩFP�����Ƿ����֧������ʵ�ֹ�ҵ�����Ӧ�ã������������ʵ��һ��β�ݹ�Ľ׳ˣ�
����python:


# I tested that python doesn't support tail recursion.
# as below:
# def fact(n):
# if(n==0):
# return 1
# else:
# return n*fact(n-1)
# if we call fact(1000), the error is:
# RuntimeError: maximum recursion depth exceeded

��Ȼpython����map, reduce, filter�ȵȿ���functional�Ķ������������õIJ���FPģ�͡�

--
LIU
https://sites.google.com/site/algoxy/home

On Feb 12, 10:51 am, ʯ��� <shiqi...@gmail.com> wrote:
> ����"���ij���Scheme��������
> �������ʱ����ջ�������������ϸ㣬��Ȼ����ջ���"��˼·,
> c#�ı�����Ϊʲô���Ѻ�����õ��ֳ������ڶ���?
> ������ջ�������ʱ����ֳ��ı���ת������,����Ļ������д�IJ���һ�����޵ĵݹ����һ���Ƚ���ĵݹ�,���ܿ����ܳ����,�����ڳ����һ���е�ʱ��ͱ�ջ�ռ����?
>
> �� 2011��2��12�� ����10:48��Jeffrey Zhao <je...@live.com>���
>
> > ���ˣ���ûд��β�ݹ飬C#��ô���Ż������Scheme�汾Ҳû���Ż��������ǻ��ڴ治�㡣


>
> > Jeffrey Zhao
> > Blog:http://blog.zhaojie.me/
> > Twitter:http://twitter.com/jeffz_cn
>

> > *From:* ʯ��� <shiqi...@gmail.com>


> > *Sent:* Saturday, February 12, 2011 10:45 AM
> > *To:* pon...@googlegroups.com

> > *Subject:* Re: [TL] ������õ�ʱ��,�ֳ�һ��Ҫ�����ڲ���ϵͳ�ṩ��ջ�ռ�����ô?
>
> > ��,��Ҳ���ҵĿ���.����Ϊʲô��ô��Ȼ���Ż�,c#����?
>
> > �� 2011��2��12�� ����10:04��Jeffrey Zhao <je...@live.com>���
>
> >> �������C#��������Ͳ���β�ݹ飬ִ��ʱ���Ҫ���ϱ���ռ�ռ��ˡ�����DrScheme��˭֪��������ôִ�е��ء����ij��д��Scheme���������������ʱ����ջ�������������ϸ㣬��Ȼ����ջ��������ǻ��ڴ��þ��ˡ�
>
> >> ���������β�ݹ��Ż��ģ������ջ��������Ҳ�������¿ռ�ȵȣ���������ѭ��������ռ���ڴ档

jinhu wang

unread,
Feb 11, 2011, 10:44:09 PM2/11/11
to pon...@googlegroups.com
首先,堆内存的维护者是用户,谁申请谁释放。栈内存的维护者是系统。
然后函数的临时变量调用过程不放在栈里面,那栈还能干什么?
最后,如果你用vxworks操作系统,它提供给你一个taskInit函数,你可以指定你申请的堆内存作为任务的栈空间。

qiaojie

unread,
Feb 11, 2011, 10:50:06 PM2/11/11
to pon...@googlegroups.com


2011/2/12 jinhu wang <wangji...@gmail.com>
首先,堆内存的维护者是用户,谁申请谁释放。栈内存的维护者是系统。
 
这个说法是错误的,对于有GC的语言来说,申请和释放是2个正交的过程,不存在谁申请谁释放的问题。栈内存的维护者是系统这说法也很有问题,系统为程序开辟了栈空间,但是维护是程序自己维护的,实际上是编译器生成的函数调用代码在维护。
 

jinhu wang

unread,
Feb 11, 2011, 11:30:27 PM2/11/11
to pon...@googlegroups.com
较真:)
有GC的语言指的是啥语言?做这么个实验吧,启动一个线程,你在线程里用你的容器(带GC的)申请例如1M堆内存。在外边把这个线程强行终止。然后你看看内存是不是释放了。
至于栈空间,一般是创建线程的时候,系统调用(例如createthread)申请了一段内存(大小或者缺省或者指定)作为栈空间,有的系统提供灵活的api,你可以申请好一段堆内存直接作为线程的初始化参数来替代系统自己申请的动作。后者的好处是你可以很好的加一些栈越界判断之类的机制到里面,也有利于内存管理。

机械唯物主义 : linjunhalida

unread,
Feb 11, 2011, 11:44:45 PM2/11/11
to pon...@googlegroups.com
是的, 栈大小程序本身也是可以改的.

2011/2/12 qiaojie <qia...@gmail.com>:

jinhu wang

unread,
Feb 11, 2011, 11:47:34 PM2/11/11
to pon...@googlegroups.com
如果不是启动的时候,how?

Xuefeng Chang

unread,
Feb 11, 2011, 11:54:52 PM2/11/11
to pon...@googlegroups.com
_resetstkoflw 算吗?

jinhu wang

unread,
Feb 12, 2011, 12:05:50 AM2/12/11
to pon...@googlegroups.com

The _resetstkoflw function recovers from a stack overflow condition, allowing a program to continue instead of failing with a fatal exception error. If the _resetstkoflw function is not called, there are no guard page after the previous exception. The next time that there is a stack overflow, there are no exception at all and the process terminates without warning.

If a thread in an application causes an EXCEPTION_STACK_OVERFLOW exception, the thread has left its stack in a damaged state. This is in contrast to other exceptions such as EXCEPTION_ACCESS_VIOLATION or EXCEPTION_INT_DIVIDE_BY_ZERO, where the stack is not damaged. The stack is set to an arbitrarily small value when the program is first loaded. The stack then grows on demand to meet the needs of the thread. This is

Larry, LIU Xinyu

unread,
Feb 12, 2011, 1:00:54 AM2/12/11
to TopLanguage
Hi,

I can't read the content because of wrong encoding. Do you mean the
fact function can compute 1000! in your machine?
I am currently using Python 2.5, it report this error at least in my
PC.

BTW: the python version of reduce, map, and filter internally use
iterative implementation so that they seem can recurs over big
numbers. For instance:

>>> reduce(lambda x, y: x+y, range(100000))
4999950000L

see Python-2.6.5\Modules\_functoolsmodule.c line 12

--
LIU
https://sites.google.com/site/algoxy/home

On Feb 12, 11:40 am, "Jeffrey Zhao" <je...@live.com> wrote:
> def fact(n):
>     if(n==0):
>         return 1
>     else:
>         return n*fact(n-1)
>
> Ȼ ȷPython һ β ݹ Ż ȷ β ݹ顣
>

> Ϊ ģ һ ֣ ִ л һ ֡ Ϊ ˱ ֤ ܵ أ û 취˭ ÷ ŵ ǹ ʽ ģFP Ҳ п ջ ġ Ի Ҫ ס ض ԡ Ĵ ?ʽ ú ʽ ʽ л̫ ˡ


>
> Jeffrey Zhao
> Blog:http://blog.zhaojie.me/
> Twitter:http://twitter.com/jeffz_cn
> -----ԭʼ ʼ -----
> From: Larry, LIU Xinyu
> Sent: Saturday, February 12, 2011 11:19 AM
> To: TopLanguage
> Subject: [TL] Re: õ ʱ , ֳ һ Ҫ ڲ ϵͳ ṩ ջ ռ ô?
>
> Hi,
>
> ׿ β ݹ鲻˵
>
> ʵ ĵļ SICP ֵ Ļ ģ һ ¾ ˣ
>

> FP Ե ģ ͺ procedural Ե ģ Dz ͬ ģ assume û Ҳ dz Ա ʹ õݹ顣ʶ ʹ ջ 򻯱 ʵ ֡


> Ҽǵõ DOS ʹ Turbo Pascalʱ Ե ֲ ȷ ݹ ʹ ò ó 7 㣬 ᷢ ջ
>
> ٸ һ ⣬ ܾ 䡣 һ ˡ
>
> Թ ⣬ ֪ ȵ ʵ ǵݹ ʵ ֡ ݹ鲻 β ݹ顣
> DOSʱ Թ ɿ ״ 7 ʶ ݹ һ ʱ Ŀ ǵĵ ͽⷨʹ stack
> ÿ չʱ ѵ ǰ ߷ ѹջ Ȼ ݵ ʱ 򵯳
>
> һ ǰ˻ʺ 8 ñ 7 һ ʹ DFS ˻ʺ󣬾ͱ ʹ Զ stack
>

> ǿ һ 㣬 ෶ʽ Ҳ ȿֺ ļ FP һЩ ⣬ Ҳ Ǹ ʱ ְɡ DZ ļ ģprocedural ġ


> ж ЩFP Ƿ ֧ ʵ ֹ ҵ Ӧ ã ʵ һ β ݹ Ľ׳ˣ
> python:
> # I tested that python doesn't support tail recursion.
> # as below:
> # def fact(n):
> #     if(n==0):
> #         return 1
> #     else:
> #         return n*fact(n-1)
> # if we call fact(1000), the error is:
> # RuntimeError: maximum recursion depth exceeded
>
> Ȼpython map, reduce, filter ȵȿ functional Ķ õIJ FPģ ͡
>
> --

> LIUhttps://sites.google.com/site/algoxy/home


>
> On Feb 12, 10:51 am, ʯ <shiqi...@gmail.com> wrote:
>
> > " ij д Scheme
> > ʱ ջ ϸ㣬 Ȼ ջ " ˼·,
> > c# ı Ϊʲô Ѻ õ ֳ ڶ ?

> > ջ ʱ ֳ ı ת , Ļ д IJ һ ޵ĵݹ һ Ƚ ĵݹ, ܿ ܳ , ڳ һ е ʱ ͱ ջ ռ ?


>
> > 2011 2 12 10:48 Jeffrey Zhao <je...@live.com
>
> > >   ˣ ûд β ݹ飬C# ô Ż Scheme 汾Ҳû Ż ǻ ڴ治 㡣
>
> > > Jeffrey Zhao
> > > Blog:http://blog.zhaojie.me/
> > > Twitter:http://twitter.com/jeffz_cn
>
> > >  *From:* ʯ <shiqi...@gmail.com>
> > > *Sent:* Saturday, February 12, 2011 10:45 AM
> > > *To:* pon...@googlegroups.com
> > > *Subject:* Re: [TL] õ ʱ , ֳ һ Ҫ ڲ ϵͳ ṩ ջ ռ ô?
>
> > > , Ҳ ҵĿ . Ϊʲô ô Ȼ Ż ,c# ?
>
> > > 2011 2 12 10:04 Jeffrey Zhao <je...@live.com
>

> > >> C# Ͳ β ݹ飬ִ ʱ Ҫ ϱ ռ ռ ˡ DrScheme ˭֪ ôִ еء ij д Scheme ʱ ջ ϸ㣬 Ȼ ջ ǻ ڴþ ˡ

Jeffrey Zhao

unread,
Feb 12, 2011, 1:06:17 AM2/12/11
to pon...@googlegroups.com
I mean, the code is not tail recursion:

def fact(n):
if(n==0):
return 1
else:
return n*fact(n-1)

-----原始邮件-----
From: Larry, LIU Xinyu
Sent: Saturday, February 12, 2011 2:00 PM
To: TopLanguage
Subject: [TL] Re: 函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?

石奇偲

unread,
Feb 12, 2011, 1:10:03 AM2/12/11
to pon...@googlegroups.com
正如我在首帖里说的,我们这里说的栈特指的是系统栈,而不是普通数据结构意义上的栈,
直觉上,我觉得对系统栈的操作都应该放到核心态里面运行,因为一般情况下用户没有理由显式的操作系统栈(一个函数被调用之后,从另外一个函数返回?)
不过刚才查了一下,有一些函数也可以对系统栈进行操作.但是封装的很严.
不知道在汇编级或者更底层有没有可以直接访问系统栈的方法,总之直接操作系统栈我觉得是很不可思议的事情.

在 2011年2月12日 上午11:50,qiaojie <qia...@gmail.com>写道:

qiaojie

unread,
Feb 12, 2011, 1:18:52 AM2/12/11
to pon...@googlegroups.com
这位同学做技术靠直觉是不行的,你这样乱猜乱试对自己的提高没什么好处,推荐你一本书深入理解计算机系统》,耐心把这本书读上几遍,你要的答案都可以在里面找到。

 
2011/2/12 石奇偲 <shiq...@gmail.com>

石奇偲

unread,
Feb 12, 2011, 1:24:06 AM2/12/11
to pon...@googlegroups.com
恩,这是本好书,而且没有把它读完.
好吧,我看书去了.

jinhu wang

unread,
Feb 12, 2011, 1:41:15 AM2/12/11
to pon...@googlegroups.com
别受他影响,你的直觉是对的!

Larry, LIU Xinyu

unread,
Feb 12, 2011, 2:10:15 AM2/12/11
to TopLanguage
Hi,

What about this one?

def fact(n, acc = 1):
if n == 0:
return acc
else:
return fact(n-1, acc*n)

>>> fact(5)
120
>>> fact(1000)

Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
fact(1000)
.....
RuntimeError: maximum recursion depth exceeded.

Cheers.
--
LIU
https://sites.google.com/site/algoxy/home

up duan

unread,
Feb 12, 2011, 2:10:58 AM2/12/11
to pon...@googlegroups.com
在栈这个问题上,jinhu wang的说法是不正确的,而qiaojie的说法是正确的。
栈确实只是应用程序运行时单入口单出口函数的层次式调用形成的Frame一层层垒起来的。实际上,Stack的所有的高级特性只不过是依赖于后进先出性。所谓的释放和分配不过是修改SP【栈顶指针】而已,当然,对于有构造和析构的C++来说,还要增加一些额外的操作。

2011/2/12 jinhu wang <wangji...@gmail.com>

red...@gmail.com

unread,
Feb 12, 2011, 4:22:36 AM2/12/11
to pon...@googlegroups.com
离题一下, 似乎现在 CLR 虚拟机性能上比起 SUN JDK 的差距, 和语言有关的部分, 有两点影响还是有点大的, 一个是尾递归的识别和优化, 一个是虚函数的 inline 话, JDK 都已经做了.

于 2011/2/12 10:56, qiaojie 写道:
真把函数调用的现场保存在堆里你又要抱怨C#性能太差了


 


Jeffrey Zhao

unread,
Feb 12, 2011, 4:29:15 AM2/12/11
to pon...@googlegroups.com
虚函数的内联,主流JVM是做了。
 
尾调用优化,是CLR做了,JVM不支持。
 
Sent: Saturday, February 12, 2011 5:22 PM
Subject: Re: [TL] 函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?
 

up duan

unread,
Feb 12, 2011, 4:34:27 AM2/12/11
to pon...@googlegroups.com
JVM似乎还有这样一个优化:

lock;
....code...
unlock;
lock;
...code...
unlock;

这样的代码模式如果code非常小,那么会优化为
lock;
....code...
...code...
unlock;

而CLR没有做这种优化。

2011/2/12 Jeffrey Zhao <je...@live.com>

qiaojie

unread,
Feb 12, 2011, 4:45:58 AM2/12/11
to pon...@googlegroups.com
这种优化会改变程序行为的,太危险了。

 
2011/2/12 up duan <fix...@gmail.com>

up duan

unread,
Feb 12, 2011, 4:53:12 AM2/12/11
to pon...@googlegroups.com
这是运行时优化,跟inline virtual一样,逻辑上的改变跟实际上的不变是一致的,不会有危险。

2011/2/12 qiaojie <qia...@gmail.com>

lzprgmr

unread,
Feb 12, 2011, 5:00:28 AM2/12/11
to pon...@googlegroups.com
我试了一下,在C++中是会对尾递归做优化,从而不会stack overflow~

int factorial_tail_recursion(int n, int acc)
{
if (n == 1)
{
return acc;
}

return factorial_tail_recursion(n-1, acc * n);
}

int factorial(int n)
{
if (n == 1)
{
return 1;
}

return n * factorial(n-1);
}

int main(int argc, const char *argv[])
{

// no stack overflow
int v1 = factorial_tail_recursion(1000000000, 1);
cout << v1 << endl;


// stack overflow
int v2 = factorial(1000000000);

cout << v2 << endl;

return 0;
}

Jeffrey Zhao

unread,
Feb 12, 2011, 5:17:39 AM2/12/11
to pon...@googlegroups.com
That's just what I said - I know that python does no tail call optimization,
but the code you showed at the first time is not tail recursion. :D

Jeffrey Zhao
Blog: http://blog.zhaojie.me/
Twitter: http://twitter.com/jeffz_cn
-----原始邮件-----
From: Larry, LIU Xinyu

Sent: Saturday, February 12, 2011 3:10 PM

Larry, LIU Xinyu

unread,
Feb 13, 2011, 9:25:18 PM2/13/11
to TopLanguage
Hi,

This depends on platform and compiler. I guess you built it with MS/
DevStudio and actually you got it run in .Net.
Here is my test:

//-------------fact.cpp-------------
#include <iostream>

typedef long long N;
N fact(N n, N acc=1){
if(n==0) return acc; else return fact(n-1, acc*n);}

int main(){ std::cout<<fact(100000); }

And the result under different level of optimization is as the
following:
liuuuxin@wl025474 ~/temp/cpp
$ g++ fact.cpp -o fact

liuuuxin@wl025474 ~/temp/cpp
$ ./fact

>>>fact.exe.stackdump

liuuuxin@wl025474 ~/temp/cpp
$ g++ fact.cpp -O2 -o fact

liuuuxin@wl025474 ~/temp/cpp
$ ./fact
0

liuuuxin@wl025474 ~/temp/cpp
$ g++ fact.cpp -O3 -o fact

liuuuxin@wl025474 ~/temp/cpp
$ ./fact
0

No mater what optimization level, correct answer can't be output.

--
LIU
https://sites.google.com/site/algoxy/home

On Feb 12, 6:00 pm, lzprgmr <baiyanhu...@gmail.com> wrote:
> 我试了一下,在C++中是会对尾递归做优化,从而不会stack overflow~
>
> int factorial_tail_recursion(int n, int acc)
> {
> if (n == 1)
> {
>  return acc;
>
> }
>
> return factorial_tail_recursion(n-1, acc * n);
>
> }
>
> int factorial(int n)
> {
> if (n == 1)
> {
>  return 1;
>
> }
>
> return n * factorial(n-1);
>
> }
>
> int main(int argc, const char *argv[])
> {
>
> // no stack overflow
> int v1 = factorial_tail_recursion(1000000000, 1);
>  cout << v1 << endl;
>
> // stack overflow
>  int v2 = factorial(1000000000);
>
> cout << v2 << endl;
>
> return 0;
>
> }
>
> Blog:http://www.cnblogs.com/baiyanhuang/
> Douban:http://www.douban.com/people/baiyanhuang/
>
> 2011/2/12 Larry, LIU Xinyu <liuxiny...@gmail.com>

Larry, LIU Xinyu

unread,
Feb 13, 2011, 9:37:14 PM2/13/11
to TopLanguage
Hi,

I don't think relay on tail recursion optimization is a good idea when
you programming in functional way.
Compare the two `fact' programs with and without tail recursion, it is
obvious that the latter is expressive.

It's not possible to re-write all functional algorithms in tail
recursion manner. This limits the usage of functional
paradigm in procedural languages.

My suggestion is that: Think and prototype in functional way, then
evaluate if it is suitable in your host language.
If so (for example, O(lg N) deduce to very small recursion depth),
translate it directly to your implementation,
otherwise, eliminate the recursion by iteration or turn it into tail
recursion if your platform support optimization.

Larry, LIU Xinyu

unread,
Feb 13, 2011, 9:57:28 PM2/13/11
to TopLanguage

Atry

unread,
Feb 23, 2011, 12:12:12 AM2/23/11
to pon...@googlegroups.com
应用程序的栈都是用户态内存,call、push 什么的汇编指令都不用切换到内核态,应用程序想怎么用怎么用。谁告诉你是操作系统来控制的?

张国锋

unread,
Apr 2, 2011, 6:09:32 AM4/2/11
to pon...@googlegroups.com
印象里纯函数式语言是可以不用栈的,函数和变量的地位完全一致,调用函数和new 一个变量是一样的,所以最后是内存用尽而不是栈溢出(没有栈哪来栈溢出?)。

Jeffrey Zhao

unread,
Apr 2, 2011, 6:19:12 AM4/2/11
to pon...@googlegroups.com
过程式语言也可以不用栈嘛。只不过调用栈啊call什么的cpu直接支持了,方便好用。
 
From: 张国锋
Sent: Saturday, April 02, 2011 6:09 PM
Subject: Re: [TL] 函数调用的时候,现场一定要保存在操作系统提供的栈空间里面么?
 

张国锋

unread,
Apr 2, 2011, 10:41:06 AM4/2/11
to pon...@googlegroups.com
call指令和栈没有必然的联系,过程式语言用栈是因为用它保存局部变量简单且效率高。纯函数式语言实际上是没有变量的(我上个帖子中说new 一个变量是不准确的,只是说处理方式和开销有类似之处),因此调用函数时可以不用这个数据结构(但是不妨碍用call去实现函数调用)。

Jeffrey Zhao

unread,
Apr 5, 2011, 3:50:41 AM4/5/11
to pon...@googlegroups.com
这里说的栈是数据结构?用call实现函数调用了还能不用栈吗?
 
Jeffrey Zhao
Blog: http://blog.zhaojie.me/
Twitter: @jeffz_cn

phenix y

unread,
Apr 7, 2011, 7:45:13 PM4/7/11
to pon...@googlegroups.com
Continuation

张国锋

unread,
Apr 8, 2011, 10:37:23 AM4/8/11
to pon...@googlegroups.com
是我的理解有问题,你说的call应该是指狭义的call,特别是intel CPU的call指令。实际上,很多CPU是没有类似指令的,如MIPS; 有的虽然有类似指令,但实现方式不同,比如Power PC,它的bl指令和intel CPU的call功能类似,但是它不是把下一条指令放入栈,而是放入link register,而所有的栈操作必须手工完成。

你说的call指令-也即函数调用指令-实际上应该来自高级语言。计算机语言中的函数(function),最早应该是在1958年出现,分别是Fortran II 和Lisp,不过它们的含义应该是不一样的,Fortran的函数实际上是一种代码组织手段,而Lisp的函数更接近来自λ演算。而当时的计算机还十分落后,指令系统很简单(好象连变址寻址都没有)。后来才出现专为函数调用服务的call指令(称为用户级指令,当时还没有现代意义上的操作系统,专门为当时唯一的实用高级语言Fortran优化可是一大卖点)。
Reply all
Reply to author
Forward
0 new messages