该程序会为每个整数终止吗?


14

在GATE准备的零件测试中,存在一个问题:

f(n):
     if n is even: f(n) = n/2
     else f(n) = f(f(n-1))

我回答“它将终止于所有整数”,因为即使对于某些负整数,它将终止为Stack Overflow Error

但是我的朋友不同意说,由于这不是实现的代码,而只是伪代码,因此在某些负整数的情况下将是无限递归的。

哪个答案正确,为什么?


8
它不会因n = -1而终止。在这种情况下,通常会考虑理论上的限制。
Deep Joshi

9
如果将堆栈溢出视为终止,那么所有程序都将终止,这将使此问题的目的
无法实现

10
@ xuq01 while (true);不会终止,也不会导致堆栈溢出。
TripeHound18年

3
@leftaroundabout我可能不应该在任何“明智的使用“ ”,因为它是“ 明智 ” 的完全不同的级别...发现并实现尾递归是很好的(甚至是明智的),但不这样做只是有点“ 不明智” ”。while(true);任何堆栈方式实现的任何事情绝对是不明智的。关键是,除非您故意不让自己尴尬,while(true);否则不会终止也不会触发堆栈溢出。
TripeHound18年

14
@ xuq01我不认为“破坏宇宙”可以解决停顿问题。
TripeHound18年

Answers:


49

正确的答案是该函数不会针对所有整数终止(特别是,它不会以-1终止)。您的朋友说这是伪代码是正确的,并且伪代码不会在堆栈溢出时终止。伪代码没有被正式定义,但是其想法是,它确实可以做到。如果代码未显示“终止于堆栈溢出错误”,则没有堆栈溢出错误。

即使这是一种真正的编程语言,正确的答案仍然是“不终止”,除非使用堆栈是该语言定义的一部分。大多数语言没有指定程序可能会导致堆栈溢出的行为,因为很难准确知道程序将使用多少堆栈。

如果在许多解释器中的实际解释器或编译器上运行代码会导致堆栈溢出,则这是该语言的形式语义与实现之间的差异。通常可以理解,语言的实现只会执行在具有有限内存的具体计算机上可以完成的工作。如果程序死于堆栈溢出,则应该购买一台更大的计算机,必要时重新编译系统以支持所有内存,然后重试。如果程序未终止,则您可能必须永远这样做。

甚至程序不会或不会溢出堆栈的事实也没有得到很好的定义,因为某些优化(例如尾部调用优化和备忘录)可以允许在常量绑定的堆栈空间中无限次调用函数。一些语言规范甚至要求实现在可能的情况下执行尾部调用优化(这在功能编程语言中很常见)。对于此功能,f(-1)扩展为f(f(-2));的外部调用f是尾部调用,因此它不会将任何内容压入堆栈,因此仅f(-2)进入堆栈,然后返回-1,因此堆栈返回到开始时的状态。因此,通过尾部调用优化,f(-1)永久内存中将永远循环。


3
Haskell是一个将代码翻译成某种编程语言而不会导致堆栈溢出的示例。它只是无限循环:let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
JoL

5

如果我们以C语言的角度来看这件事,那么在原始程序不调用未定义行为的所有情况下,实现都可以自由地用产生相同结果的代码替换代码。所以可以代替

f(n):
   if n is even: f(n) = n/2
   else f(n) = f(f(n-1))

f(n):
   if n is even: f(n) = n/2
   else f(n) = f((n-1) / 2)

现在,实现允许应用尾递归:

f(n):
   while n is not even do n = (n-1) / 2
   f(n) = n/2

并且仅当n = -1时,此循环才会永远循环。


我认为在C语言中,调用f(-1)是未定义的行为(实现可能会假设每个线程都终止或在此函数无法执行的简短活动列表中执行其他操作),因此编译器实际上可以执行该操作案件!
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.