2

It will be plain I am not a competent writer of C, but was wondering, in connection with this question Ackermann very inefficient with Haskell/GHC (which irritating me perhaps irrationally) why the author's program to calculate Wilhelm Ackermann's famous function:

int ack(int m, int n) {
  if (m == 0) return n+1;
  if (n == 0) return ack(m-1, 1);
  return ack(m-1, ack(m, n-1));
}

int main() {
  printf("%d\n", ack(4,1));
  return 0;
}

works fine, but when given an obvious optimization (which helps elsewhere) and given the argument (4,2) -- which is of course algorithmically cruel -- ends with Segmentation fault: 11 :

int ack(int m, int n) {
  if (m == 0) return n+1;
  if (m == 1) return n+2;
  if (n == 0) return ack(m-1, 1);
  return ack(m-1, ack(m, n-1));
}

int main() {
  printf("%d\n", ack(4,2));
  return 0;
}

If I comment out the 'optimizing' line if (m == 1) return n+2;, the program runs on and on as it does in other languages, but doesn't have the same effect -- at least not after say 5 minutes of operation. [Correction, it seems it does -- after 8m41s] (I should note that I am using gcc that comes with os x, but the same seems to be happening with e.g. the gcc-4.7.2 on ideone.com.)

I agree the program doesn't deserve even to be compiled, but wonder why a segmentation fault, which is generally viewed with horror and as a language flaw or compiler fault in other languages I am familiar with, is here the appropriate response for the gcc .

Community
  • 1
  • 1
applicative
  • 8,081
  • 35
  • 38

1 Answers1

6

Both programs run out of stack and your improvement does that faster. Be aware that the required stack is proportional to the parameter 'n' and 'n' grows fast. So no bug, just a limited machine.

Let me add some code out of my archive, just for fun and more speed. It also goes to show how fast 'n' grows with each increment of 'm'.

typedef unsigned long long N;
N acker (N m, N n)
{
        while (1)
        switch (m)
        {
        case 0: return n+1U;
        case 1: return n+2U;
        case 2: return 2U*(n+1U)+1U;
        case 3: return (1LLU<<(n+3U))-3U;
        default:
                if (n == 0U)
                        n = 1U;
                else
                        n = acker(m, n-1U);
                m--;
                break;
        }
        return n+1U;
}
Bryan Olivier
  • 5,207
  • 2
  • 16
  • 18
  • well, that's certainly astonishingly fast! I was aware that my so-called optimization was making things worse in this case, if only by clouding gcc's view of the matter (though you seem to be able to see it immediately.) I will study this a bit more. – applicative Apr 24 '13 at 00:46