0

I made a program that calculates the divisor function with a recursive method. Because of too many recursions, this function was very slow without any caching. So I changed the function to use a table to remember the values that are already calculated.

Here is the program.

#include <stdio.h>
#include <stdlib.h>
int sigma(int n, int k, int *table, int size){
    if(n<0){return 0;}
    else if(n==0){return k;}
    else if(n<size && table[n-1]){return table[n-1];}
    else{
        k=0;
        for(int i=1;i*(3*i-1)<=2*n;i++){
            k += (((i&1)<<1)-1) * (sigma(n-i*(3*i-1)/2,n,table,size) + sigma(n-i*(3*i+1)/2,n,table,size));
        }
        if(n<size && !table[n-1]){table[n-1]=k;}
        return k;
    }
}

int main(int argc, char *argv[]){
    if(argc>1){
        int n = atoi(argv[1]);
        int *table = (int*)calloc(n,sizeof(int));
        if(table){
            printf("%d\n",sigma(n,n,table,n));
        }
        free(table);
    }
    return 0;
}

When the input is smaller than around 100000, this program works fine, but when it is bigger than that, EXC_BAD_ACCESS is thrown.

What is wrong with this? Thanks.

My computer is MacBook Pro, My compiler is Apple clang version 11.0.0.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 3
    What line throws it? You can use Applications -> Utilities -> Console Window and switch to the crash reporter to peruse the crash file, or you can run in a debugger. – Joseph Larson Mar 12 '21 at 16:30
  • 1
    I'd also add some range-checking in case there's something wrong with the logic. Specifically, that big ugly line of recursion -- make sure every index into table is legal. You could set a global and make sure sigma's being called with legal values, too. – Joseph Larson Mar 12 '21 at 16:33
  • 1
    recursion often looks great on paper but performance is often lacking, maybe you should consider rewriting it instead of trying jumping through hoops. – AndersK Mar 12 '21 at 16:45
  • 1
    The recursion is too deep and you get a stack overflow. I'd rewrite the algorithm without recursion. Or you can increase the stack size of your executable, read this: https://stackoverflow.com/questions/18909395/how-do-i-increase-the-stack-size-when-compiling-with-clang-on-os-x – Jabberwocky Mar 12 '21 at 16:49
  • 2
    Whether you realize or not, your table caching does nothing to stop the inevitable. There are `n` recursive calls involved when *generating* those table entries. Use a small test number, say 100, and set a breakpoint on your first successful table lookup (the `return table[n-1];`). Omce the debugger breaks, just look at the call stack. You'll see there are (n-1) invokes to get you to that position. No imagine that for 100000+ invocations. Then breathe a moment of respect for your activation stack. It did its best, and was found... lacking. – WhozCraig Mar 12 '21 at 16:58

1 Answers1

0

Thanks for helpful comments. I guess too many recursion is just bad. I rewrite the program so that it does recursion manually.

Here is the new program.

#include <stdio.h>
#include <stdlib.h>
#define STACK_SIZE 1024

/*struct Pair*/

typedef struct st_node Node;
struct st_node{
    char sign;
    int index;
    int value;
    long parentOffset;
};

void Node_init(Node *node, char sign, int index, int value, long parentOffset);
void Node_print(Node *node);

void Node_init(Node *node, char sign, int index, int value, long parentOffset){
    node->sign = sign;
    node->index = index;
    node->value = value;
    node->parentOffset = parentOffset;
    /*
    printf("Node_init : \t");
    Node_print(node);
    */
}

void Node_print(Node *node){
    printf("%d,%d,%d,%d\n",node->sign,node->index,node->value,(unsigned int)node->parentOffset);
}

/*struct Stack*/

typedef struct st_stack{
    Node *bottom;
    Node *top;
    int size;
}Stack;

void Stack_init(Stack *stack, Node node, int size);
void Stack_finish(Stack *stack);
void Stack_push(Stack *stack, Node node);
Node Stack_pop(Stack *stack);
int Stack_resize(Stack *stack, int size);
int Stack_size(Stack *stack);
void Stack_print(Stack *stack);

void Stack_init(Stack *stack, Node node, int size){
    stack->bottom = stack->top = (Node*)malloc(size*sizeof(Node));
    *(stack->top) = node;
    stack->size = size;
}

void Stack_finish(Stack *stack){
    free(stack->bottom);
    stack->size = 0;
}

void Stack_push(Stack *stack, Node node){
    if(stack->top - stack->bottom < stack->size-1){
        *(++stack->top) = node;
    }else{
        if(Stack_resize(stack,2*stack->size)){
            Stack_push(stack,node);
        }
    }
}

Node Stack_pop(Stack *stack){
    if(stack->top==stack->bottom){
        return *(stack->top);
    }else{
        return *(stack->top--);
    }
}

int Stack_resize(Stack *stack, int size){
    Node *nbottom = (Node*)realloc(stack->bottom,size*sizeof(Node));
    if(nbottom){
        printf("Stack_resize : %d\n",size);
        stack->top = nbottom + (stack->top - stack->bottom);
        stack->bottom = nbottom;
        stack->size = size;
        return 1;
    }else{
        printf("Stack_resize failed with size : %d\n",size);
        return 0;
    }
}

int Stack_size(Stack *stack){
    return stack->top-stack->bottom + 1;
}

void Stack_print(Stack *stack){
    printf("stack size : %d\n",Stack_size(stack));
    for(Node *node=stack->bottom;node<=stack->top;node++){
        Node_print(node);
    }
}

int odd(int x){return ((x&1)<<1)-1;}

int sigma(int n){
    int *table;
    table = (int*)calloc(n,sizeof(int));
    
    Stack stack;    
    Node node;
    Node_init(&node,1,n,0,0);
    Stack_init(&stack,node,STACK_SIZE);
    int maxstack=0;
    
    for(;;){
        Node *top = stack.top;
        Node *parent = stack.bottom+top->parentOffset;
        long offset = top - stack.bottom;
        if(top->value){
            if(!table[top->index-1]){table[top->index-1]=top->value;}
            if(Stack_size(&stack)>1){
                parent->value += top->sign * top->value;
                Stack_pop(&stack);
            }else{
                break;
            }
        }else{
            if(top->index==0){
                if(Stack_size(&stack)>1){
                    parent->value += top->sign * parent->index;
                    Stack_pop(&stack);
                }else{
                    break;
                }
            }else if(top->index<0){
                Stack_pop(&stack);
            }else{
                if(table[top->index-1]){
                    parent->value += top->sign * table[top->index-1];
                    Stack_pop(&stack);
                }else{
                    /*add stack*/
                    for(int i=1;i*(3*i-1)<=2*top->index;i++){
                        Node_init(&node,odd(i),(top->index-i*(3*i-1)/2),0,offset);
                        Stack_push(&stack,node);
                        Node_init(&node,odd(i),(top->index-i*(3*i+1)/2),0,offset);
                        Stack_push(&stack,node);
                    }
                }
            }
        }
        if(maxstack<Stack_size(&stack)){maxstack=Stack_size(&stack);}
        /*
        for(int i=0;i<n;i++){
            printf("%d,",table[i]);
        }
        printf("\n");
        Stack_print(&stack);
        */
    }
    //printf("max stack size : %d\n",maxstack);
    int value = stack.top->value;
    Stack_finish(&stack);
    free(table);
    return value;
}

int main(int argc, char *argv[]){
    if(argc>1){
        int n = atoi(argv[1]);
        printf("%d\n",sigma(n));
    }
    return 0;
}

I realized that recursion in this program needs a tree or something similar structure to remember the chain of function calls. I guess the original program makes the compiler build this tree on stack memory. So too many recursion caused a stack overflow.

  • Yes, right around 100K recursions you will overflow the stack space (on Linux with a 4M stack) and attempt to access memory outside that memory segment prompting the bad access (or SegFault). So you came to the right place with your StackOverflow question `:)` Many of the recursive algorithms line merge-sort, etc. are vulnerable. So for large data sets or iterations, you need to find a procedural solution that avoids recursion. – David C. Rankin Mar 14 '21 at 07:01