开发者

Regarding stack reuse of a function calling itself?

开发者 https://www.devze.com 2022-12-20 03:34 出处:网络
if a function calls itself while defining variables at the same time would it result in stack overflow? Is there any option in gcc to reuse the same stack.

if a function calls itself while defining variables at the same time would it result in stack overflow? Is there any option in gcc to reuse the same stack.

void funcnew(void)
{
   int a=10;
   int b=20;
   fun开发者_StackOverflow中文版cnew();
   return ;
 }

can a function reuse the stack-frame which it used earlier? What is the option in gcc to reuse the same frame in tail recursion??


Yes. See

-foptimize-sibling-calls

Optimize sibling and tail recursive calls.

Enabled at levels -O2, -O3, -Os.

Your function is compiled to:

funcstack:
.LFB0:
    .cfi_startproc
    xorl    %eax, %eax
    jmp func
    .cfi_endproc

(note the jump to func)

Reusing the stack frame when a function end by a call -- this include in its full generality manipulating the stack to put the parameters at the correct place and replacing the function call by a jump to the start of the function -- is a well known optimisation called [i]tail call removal[/i]. It is mandated by some languages (scheme for instance) for recursive calls (a recursive call is the natural way to express a loop in these languages). As given above, gcc has the optimisation implemented for C, but I'm not sure which other compiler has it, I would not depend on it for portable code. And note that I don't know which restriction there are on it -- I'm not sure for instance that gcc will manipulate the stack if the parameters types are different.


Even without defining the parameters you'd get a stackoverflow. Since the return address also is pushed onto the stack.

It is (I've learned this recently) possible that the compiler optimizes your loop into a tail recursion (which makes the stack not grow at all). Link to tail recursion question on SO


No, each recursion is a new stack frame. If the recursion is infinitely deep, then the stack needed is also infinite, so you get a stack overflow.


Yes, in some cases the compiler may be able to perform something called tail call optimization. You should check with your compiler manual. (AProgrammer seems to have quoted the GCC manual in his answer.)

This is an essential optimization when implementing for example functional languages, where such code occurs frequently.


You can;t do away with the stack frame altogether, as it is needed for the return address. unless you are using tail-recursion, and your compiler has optimised it to a loop. But to be completely technically honest, you can do away with all the variables in the the frame by making them static. However, this is almost certainly not what you want to do, and you should not do it without knowing exactly what you are doing, which as you had to ask this question, you don't.


As others have noted, it is only possible if (1) your compiler supports tail call optimization, and (2) if your function is eligible for such an optimization. The optimization is to reuse the existing stack and perform a JMP (i.e., a GOTO in assembly) instead of a CALL.

In fact, your example function is indeed eligible for such an optimization. The reason is that the last thing your function does before returning is call itself; it doesn't have to do anything after the last call to funcnew(). However, only certain compilers will perform such an optimization. GCC, for instance, will do it. For more info, see Which, if any, C++ compilers do tail-recursion optimization?

The classic material on this is the factorial function. Let's make a recursive factorial function that is not eligible for tail call optimization (TCO).

int fact(int n)
{
  if ( n == 1 ) return 1;
  return n*fact(n-1);
}

The last thing it does is to multiply n with the result from fact(n-1). By somehow eliminating this last operation, we would be able to reuse the stack. Let's introduce an accumulator variable that will compute the answer for us:

int fact_helper(int n, int acc)
{
  if ( n == 1 ) return acc;
  return fact_helper(n-1, n*acc);
}

int fact_acc(int n)
{
  return fact_helper(n, 1);
}

The function fact_helper does the work, while fact_acc is just a convenience function to initialize the accumulator variable.

Note how the last thing fact_helper does is to call itself. This CALL can be converted to a JMP by reusing the existing stack for the variables.

With GCC, you can verify that it is optimized to a jump by looking at the generated assembly, for instance gcc -c -O3 -Wa,-a,-ad fact.c:

  ...
  37                    L12:
  38 0040 0FAFC2                imull   %edx, %eax
  39 0043 83EA01                subl    $1, %edx
  40 0046 83FA01                cmpl    $1, %edx
  41 0049 75F5                  jne     L12
  ...

Some programming languages, such as Scheme, will actually guarantee that proper implementations will perform such optimizations. They will even do it for non-recursive tail calls.

0

精彩评论

暂无评论...
验证码 换一张
取 消