views:

307

answers:

9

In either java or c I can write a function like

fun(){
  fun();
}

(ignoring syntax details)

In java I get OutOfMemory exception but in C (and maybe some other languages) it seems to run forever, as if it were an infinite loop. Why don't I get OutOfMemory error here as well?

+3  A: 

Surely you'd first get a stack overflow exception (in Java at least).

Richie_W
+18  A: 

Since your function is an example of tail recursion, then most likely, the C compiler is optimizing the recursion to iteration, causing it to loop infinitely without crashing.

RTBarnard
why don't we have it in java
GK
I'm not sure 'why', but Java does not support tail call optimization, which is why you get the error in Java, but not C.
pkaeding
The Java compiler won't automatically convert the tail recursion to iteration, and though it is technically possible for some Java VMs to perform tail recursion optimization, I can't recall having had personal experience with any that do.
RTBarnard
@gurukulki: Your question should be, "Why it does that?", since in Java you actually have the expected behavior of running out of stack space whilst in C the compiler does something that is a bit obscure to the programmer. As a programmer, you should always have in mind that functions/method calls uses memory and processor time. The fact that a compiler might optimize some of the cases is just a implementation detail. As for why Java doesn't, there might be issues with garbage collection, for instance.
Bruno Brant
The reason is probably that until recently, there has not been much need for that optimization, so Sun's engineers have concentrated on other optimizations. But now that functional programing languages on the JVM are becoming popular, there is more need for it.
Esko Luontola
Isn't it better to get an exception than an infinite loop?
Tom Hawtin - tackline
In order to support functional languages better, there is (was?) a JSR out there to implement support tail-recursion looping in the JVM - I thought this was going to be in Java 7.
Software Monkey
@Tom Hawtin: Yes, but in the case of properly formed recursion (i.e. with an end guaranteed condition), the optimized version does not suffer an arbitrary depth limit nor does it pay the function call overhead on each iteration. You win some, you lose some.
dmckee
Gah, kids these days don't know they're born. In my day, any functional language compiler would already implement tail-recursion long before it hits byte code. They'll be asking for the VM to do memoisation and lazy evaluation for them too, next ;-)
Steve Jessop
+2  A: 

In C, this could be optimized as a tail-recursive call. So the call to fun() doesn't really call itself; it just restarts the function (like a goto). In other words, the compiler treats it as if it had been written like this:

void fun() {
start:
    goto start;
}

So, the stack will not grow.

Kristopher Johnson
+9  A: 

The reason why is that the C compiler is likely treating this as a tail recusive call and hence avoiding building a stack for the execution of the function. Since no stack is built up for the call it turns from recursion into a simple infinite loop execution. You can force it to build up a stack by making it head recursive

int fun() { 1 + fun(); }
JaredPar
A: 

You will get an abnormal program termination after some time. Your code contains an indefinitive recursion and each call to fun() put sadditional bytes on the stack. Depending on the size of your memory and your limits, the application will terminate after at least some 500 mil calls. This might take some time, but you will get an exceptional termination.

The java VM limits the depth of recursion to some level, therefore it will terminate soon.

Ralf Edmund
-1 it depends on the C compiler. There's nothing that says you have to get an abnormal program termination. It all depends on the code generation in the compiler.
Timothy Baldridge
No, it is not compiler dependent. This is a feature of the operating system. The OS will extend the stack segment of the application and this will (at some point) crash into the address space occupied by the heap. ZAP - Exception caused by the OS (at least with UNIX like OSes).
Ralf Edmund
@Ralf Edmund: you're missing the point: if the compiler transforms the JSR by a JMP while performing its tail recursion, that's the whole point people are making here: *nothing goes on the stack*. Do you notice the *L2: jump L2* code produced? No push on the stack here. Just a JMP.
Webinator
+2  A: 

If a programming language's implementation has tail call optimization, then it will compile that recursion into a loop. The current Java VM does not have tail call optimization, so it will end up in java.lang.StackOverflowError.

Probably some time in the future the Java VM will have tail call optimization, because the functional programming languages which run on JVM (Scala, Clojure etc.) would benefit from it (right now at least the Scala compiler does its own tail call optimization for direct recursion, but AFAIK not for indirect recursion).

Esko Luontola
+1  A: 

Your c compiler is probably using using tail recursion. Every time you enter into a new function the computer adds an entry to the stack. This entry states where the CPU should jump back to after the routine is done. Now in the case given above, since the call to fun() inside fun() is the last call in the function the c compiler may be optimzing out the stack push and instead and creating a tailcall. You can actually use this to create a loop:

int foo(int from, int to)
{
    if (from == to) return from;
    dosomething();
    from ++;
    foo(from, to);
}

Many languages (for example Erlang) don't have loops at all and instead use the above method to create loops.

Java does not support tail recursion.

Timothy Baldridge
What java might not support is tail-recursion "optimization" but it does support tail-recursion
OscarRyz
I stand corrected...although how does it support tail-recursion if it doesn't support the method above?
Timothy Baldridge
+4  A: 

As others have pointed out this is a tail call recursion optimization done by the C compiler. As always, it helps to look at a concrete example. Without any optimizations enabled gcc (v3.4.6) produces the following x86 assembly code:-

fun:
    pushl   %ebp
    movl    %esp, %ebp
    call    fun
    leave
    ret
    .size   fun, .-fun

Notice the recursive call to fun(). If this executes it will eventually overflow its stack and crash, but at -O2 gcc produces:-

    fun:
        pushl   %ebp
        movl    %esp, %ebp
.L2:
        jmp     .L2
        .size   fun, .-fun

Notice the endless loop without a return instruction? This will simply execute forever.

Andrew O'Reilly
+9  A: 

Other answerers are correct that there's some compiler magic that converts the tail recursion to iteration, though it depends on the optimization settings of the compiler. In gcc for instance, if we compile with gcc -S -O1 someFile.c (given your code), we get the following generated assembly:

fun:
.LFB2:
        pushq   %rbp
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        movl    $0, %eax
        call    fun
        leave
        ret
.LFE2:
        .size   fun, .-fun

So you can see, it's still using call/leave/ret instructions to perform an actual function call, which will kill the process. Once you start optimizing further with gcc -S -O2 someFile.c we start getting the magic:

fun:
.LFB24:
        .p2align 4,,10
        .p2align 3
.L2:
        jmp     .L2
.LFE24:    
        .size   fun, .-fun
        .p2align 4,,15

It depends on your compiler and your compiler settings, so it helps to be friends with them.

Marc Bollinger
+1 for showing assembly and that there still exist some programmers who understand it.
Timothy Baldridge
+1 For being friends with the compiler. :-)
Thomas Matthews