2015-10-07 19 views
6

Stavo guardando un video su CUDA e l'algoritmo Barnes-Hut in cui è stato affermato che è necessario posizionare un limite di profondità sull'albero per la GPU, e quindi l'idea mi è venuta in mente probabilmente facendo ricorsione nell'heap.Ricorsione con allocazione di memoria

Fondamentalmente, mi stavo chiedendo proprio questo: è possibile allocare memoria dall'heap e usarla come "stack" temporaneo in cui posizionare le chiamate di funzione per la funzione ricorsiva in questione per ritardare un overflow dello stack?

Se sì, come potrebbe essere implementato, dovremmo allocare lo spazio per un puntatore alla funzione? Presumo che implicherebbe l'archiviazione dell'indirizzo di funzione nell'heap, ma non ne sono troppo sicuro.

[modifica] Volevo solo aggiungere che questa è una domanda puramente teorica, e immagino che facendo ciò si rallenterebbe il programma una volta che si utilizza l'heap.

[modifica] Secondo la richiesta, il compilatore che sto usando è 4.8.4 GCC su Ubuntu 14.04 (64-bit)

+2

C non definisce alcun meccanismo di questo tipo. Infatti, non definisce affatto "stack" o "heap", sotto questi o altri nomi. Sono nomi standard per gli aspetti della maggior * implementazione *, ma non rientrano nell'ambito di applicazione di C. Da un lato, ciò significa che è necessario specificare una particolare implementazione per ottenere una risposta utile. D'altra parte, significa anche che la stessa C non proibisce ciò che descrivi. In terzo luogo, non sono a conoscenza di alcuna implementazione che lo fornisce. –

+0

@JohnBollinger Spiacente, non capisco cosa intendi quando dico che dovrò specificare una particolare implementazione. Intendi come la lingua stessa si occupa della memoria? Ho preso solo alcuni corsi introduttivi fino ad ora e non sono entrato in questo aspetto oltre alle basi su come il compilatore C (e credo che la maggior parte dei compilatori) gestisca la memoria. – Plopperzz

+0

"Specificare un'implementazione particolare" significa restringere l'ambito della domanda a qualcosa come GCC 5/glibc su Linux o MS Visual C++ 2013 su Windows 10. –

risposta

3

Certo. Questo è chiamato stile di passaggio continuo. La libreria standard lo supporta con setjmp() e longjmp() e memorizza le informazioni necessarie per ripristinare il controllo in un punto precedente in una struttura denominata jmp_buf. (Esistono diverse restrizioni su dove è possibile eseguire il ripristino.) Le memorizzeresti in una pila, che è solo una coda LIFO.

Un approccio più generale è eseguire il programma come macchina di stato e memorizzare le informazioni necessarie per tornare indietro allo stato del programma, chiamato continuazione, in una struttura dati chiamata trampolino. Un motivo comune per volerlo fare è ottenere l'equivalente della ricorsione in coda in un'implementazione che non lo ottimizzi e potrebbe rompere un sacco di spazio nello stack. Un'applicazione del mondo reale in cui qualcuno che conosco sta attualmente scrivendo un trampolino è un parser GLL in cui la grammatica è rappresentata come un grafico diretto, il risultato del parse è una foresta di analisi condivisa e il parser ha spesso bisogno di tornare indietro per provare un Regola diversa

continuazione-passing e tappeti elastici sembrano essere considerato come lo stile di fantasia perché provengono dal mondo della programmazione funzionale, mentre longjmp() è considerato come un brutto hack di basso livello e anche la pagina man di Linux dice di non usarlo.

+0

Non penso che le continuazioni rispondano veramente alla domanda posta, poiché non alterano ciò che il sistema usa per una pila per le chiamate successive, né nel codice diretto né nella continuazione. Nel caso in cui si desideri una continuazione, tuttavia, è importante notare che 'setjmp()'/'longjmp()' li supporta solo in un modo limitato, dato che una volta che il controllo lascia la funzione chiamata 'setjmp()', il il comportamento di un longjmp corrispondente non è più definito. –

+0

È un mezzo alternativo per controllare il flusso dalle chiamate e dai ritorni delle funzioni. Detto questo, il tuo commento su 'longjmp()' non è corretto: il comportamento non è definito se la funzione originale * è terminata *, non appena il controllo lo abbandona. È legale 'longjmp()' da una funzione chiamata ricorsivamente da quella che ha anche chiamato 'setjmp()'. In effetti, quello era il caso d'uso per cui 'goto' non era abbastanza. – Davislor

+1

Per "control leaves" intendevo effettivamente che la funzione ritorna o esce chiamando 'longjmp()', al contrario di chiamare (altre) funzioni. Ad ogni modo, la domanda dell'OP riguarda specificamente l'utilizzo di una diversa regione di memoria per lo stack di chiamate. Nella misura in cui le continuazioni sono un * alternativa * per chiamare/tornare, proprio per questo non affrontano la domanda. –

1

È possibile simulare ciò implementando il proprio stack basato su heap come una matrice di strutture, con ogni struttura che rappresenta uno stack frame che contiene l'equivalente di parametri e variabili locali. Invece di una funzione che si chiama in modo ricorsivo, la funzione si ripete e ogni "chiamata" spinge esplicitamente un nuovo frame nello stack.

Ho fatto esattamente questo anni fa mentre tentavo di risolvere un semplice gioco da tavolo. Il programma era originariamente ricorsivo, e ci volle un'eternità per correre. L'ho modificato nella struttura di cui sopra, e questo ha reso semplice rendere l'app interrompibile/riavviabile. Una volta interrotta, l'app ha scaricato il suo "stack" in un file di stato. Al riavvio, l'app ha caricato il file di stato e ha continuato da dove era stato interrotto.

Ciò richiede un po 'di attenzione se la struttura del frame dello stack contiene dei puntatori integrati, ma non è insormontabile.