2016-03-15 9 views
5

Bei massiv rekursiven Methodenaufrufen muss die Call-Stack-Größe erweitert werden, indem die entsprechenden Compiler-Parameter modifiziert werden, um einen Stack-Overflow zu vermeiden.Erweitern Sie Callstack in C++ auf die Festplatte?

Betrachten wir eine portable Anwendung, deren Layout einfach genug ist, so dass ihre Benutzer nur minimale technische Kenntnisse besitzen müssen, so dass eine manuelle Konfiguration des virtuellen Speichers nicht in Frage kommt.

Das Ausführen massiv-rekursiver Methoden (offensichtlich hinter den Kulissen) kann dazu führen, dass das Call-Stack-Limit überschritten wird, insbesondere wenn die Maschine, auf der die Anwendung ausgeführt wird, speichermäßig begrenzt ist.

Genug chit-chat: In C++ ist es möglich, den Call-Stack manuell auf Festplatte zu erweitern, wenn der Speicher (fast) voll ist?

+1

Nein, das ist nicht möglich. Rewrite ohne Rekursion. – molbdnilo

+3

Rekursion in Iteration umwandeln, Problem gelöst. –

+2

Und nein, Sie können den Call-Stack auch nicht in "die Cloud" erweitern. – molbdnilo

Antwort

6

Es kann nur kaum möglich sein.

Verwenden Sie eine Coroutine-Bibliothek. Damit weisen Sie Ihren eigenen Stack dem Heap zu. Strukturieren Sie Ihren Code neu, um zu verfolgen, wie tief er in seinem Callstack ist, und wenn er gefährlich tief wird, erstellen Sie einen neuen Cothread und wechseln Sie stattdessen in diesen. Wenn der Heapspeicher aufgebraucht ist, frieren Sie alte cothreads ein und geben Sie den Speicher frei. Natürlich solltest du sie besser an der gleichen Adresse auftauen lassen - also schlage ich vor, dass du ihre Stapel selbst aus deiner Arena kommst, die du kontrollieren kannst. In der Tat kann es einfacher sein, das gleiche Stück Speicher für den Cothread-Stapel einfach wiederzuverwenden und sie einzeln nacheinander ein- und auszuwechseln.

Es ist sicherlich einfacher, Ihren Algorithmus als nicht rekursiv umzuschreiben.

Dies kann ein Beispiel dafür arbeiten, oder es kann nur die richtige Antwort auf das Unfallgeschehen drucken:

#include <stdio.h> 
#include "libco.h" 

//byuu's libco has been modified to use a provided stack; it's a simple mod, but needs to be done per platform 
//x86.c: 
////if(handle = (cothread_t)malloc(size)) { 
//handle = (cothread_t)stack; 

//here we're going to have a stack on disk and have one recursion's stack in RAM at a time 
//I think it may be impossible to do this without a main thread controlling the coroutines, but I'm not sure. 

#define STACKSIZE (32*1024) 
char stack[STACKSIZE]; 

FILE* fpInfiniteStack; 
cothread_t co_mothership; 

#define RECURSING 0 
#define EXITING 1 
int disposition; 

volatile int recurse_level; 

int call_in_cothread(int (*entrypoint)(int), int arg); 

int fibo_b(int n); 
int fibo(int n) 
{ 
    if(n==0) 
     return 0; 
    else if(n==1) 
     return 1; 
    else { 
     int a = call_in_cothread(fibo,n-1); 
     int b = call_in_cothread(fibo_b,n-2); 
     return a+b; 
    } 
} 
int fibo_b(int n) { printf("fibo_b\n"); return fibo(n); } //just to make sure we can call more than one function 

long filesize; 
void freeze() 
{ 
    fwrite(stack,1,STACKSIZE,fpInfiniteStack); 
    fflush(fpInfiniteStack); 
    filesize += STACKSIZE; 
} 
void unfreeze() 
{ 
    fseek(fpInfiniteStack,filesize-STACKSIZE,SEEK_SET); 
    int read = fread(stack,1,STACKSIZE,fpInfiniteStack); 
    filesize -= STACKSIZE; 
    fseek(fpInfiniteStack,filesize,SEEK_SET); 
} 

struct 
{ 
    int (*proc)(int); 
    int arg; 
} thunk, todo; 

void cothunk() 
{ 
    thunk.arg = thunk.proc(thunk.arg); 
    disposition = EXITING; 
    co_switch(co_mothership); 
} 

int call_in_cothread(int (*proc)(int), int arg) 
{ 
    if(co_active() != co_mothership) 
    { 
     todo.proc = proc; 
     todo.arg = arg; 

     disposition = RECURSING; 
     co_switch(co_mothership); 
     //we land here after unfreezing. the work we wanted to do has already been done. 
     return thunk.arg; 
    } 

NEXT_RECURSE: 
    thunk.proc = proc; 
    thunk.arg = arg; 
    cothread_t co = co_create(stack,STACKSIZE,cothunk); 
    recurse_level++; 
NEXT_EXIT: 
    co_switch(co); 

    if(disposition == RECURSING) 
    { 
     freeze(); 
     proc = todo.proc; 
     arg = todo.arg; 
     goto NEXT_RECURSE; 
    } 
    else 
    { 
     recurse_level--; 
     unfreeze(); 
     if(recurse_level==0) 
      return thunk.arg; //return from initial level of recurstion 
     goto NEXT_EXIT; 
    } 

    return -666; //this should not be possible 
} 

int main(int argc, char**argv) 
{ 
    fpInfiniteStack = fopen("infinite.stack","w+b"); 
    co_mothership = co_active(); 
    printf("result: %d\n",call_in_cothread(fibo,10)); 
} 

Jetzt müssen Sie nur, wie viel Speicher die im System erkennen, wie viel davon ist verfügbar , wie groß der Callstack ist und wann der Callstack erschöpft ist, damit Sie wissen, wann Sie den unendlichen Stack bereitstellen müssen. Das ist nicht einfach für ein System, geschweige denn portabel. Es wäre vielleicht besser zu lernen, wie der Stack eigentlich verwendet werden soll, anstatt ihn zu bekämpfen.

+0

Ausgezeichnetes Beispiel, sehr hilfreich! :) –

1

Es ist machbar. Sie benötigen ein wenig Assembly, um den Stack-Pointer zu manipulieren, da es keine standardisierte Möglichkeit gibt, direkt von C++ aus darauf zuzugreifen (soweit ich weiß). Sobald Sie dort sind, können Sie auf Ihre Speicherseite zeigen und darauf achten, Speicher ein- und auszuwechseln. Es gibt bereits Bibliotheken, die es für dich machen.

Auf der anderen Seite, wenn der Systemanbieter dachte, dass Paging-Speicher oder die anderen virtuellen Speichertechniken nicht funktionieren/auf der Plattform wert sein würden, hatten sie wahrscheinlich einen sehr guten Grund (am wahrscheinlichsten wäre es unglaublich langsam). Versuchen Sie, Ihre Lösung ohne Rekursion zum Laufen zu bringen, oder ändern Sie sie, damit die Rekursion in das vorhandene Angebot passt. Selbst eine weniger effiziente Implementierung würde schneller enden als die Rekursion auf der Festplatte.