Che cos'è lo svolgersi dello stack? Ho cercato ma non sono riuscito a trovare una risposta illuminante!
Che cos'è lo svolgersi dello stack? Ho cercato ma non sono riuscito a trovare una risposta illuminante!
Risposte:
Di solito viene discusso lo stack svolgendo in relazione alla gestione delle eccezioni. Ecco un esempio:
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
In questo caso la memoria allocata pleak
andrà persa se viene generata un'eccezione, mentre la memoria allocata s
verrà correttamente rilasciata dal std::string
distruttore in ogni caso. Gli oggetti allocati nello stack vengono "srotolati" quando si esce dall'ambito (qui l'ambito è della funzionefunc
). Questo viene fatto dal compilatore che inserisce le chiamate ai distruttori di variabili automatiche (stack).
Ora questo è un concetto molto potente che porta alla tecnica chiamata RAII , ovvero Resource Acquisition Is Initialization , che ci aiuta a gestire risorse come memoria, connessioni al database, descrittori di file aperti, ecc. In C ++.
Ora ciò ci consente di fornire garanzie di sicurezza eccezionali .
delete [] pleak;
viene raggiunto solo se x == 0.
Tutto ciò riguarda C ++:
Definizione : quando si creano oggetti staticamente (nello stack anziché allocarli nella memoria dell'heap) e si eseguono chiamate di funzione, vengono "impilati".
Quando si esce da un ambito (qualsiasi cosa delimitato da {
e }
) (usando return XXX;
, raggiungendo la fine del campo o lanciando un'eccezione) tutto ciò che è all'interno di quel campo viene distrutto (i distruttori sono chiamati per tutto). Questo processo di distruzione degli oggetti locali e di chiamata ai distruttori si chiama impilamento dello stack.
Sono presenti i seguenti problemi relativi allo svolgimento dello stack:
evitare perdite di memoria (qualsiasi cosa allocata dinamicamente che non è gestita da un oggetto locale e ripulito nel distruttore verrà trapelata) - vedere RAII a cui fa riferimento Nikolai e la documentazione per boost :: scoped_ptr o questo esempio di utilizzo boost :: mutex :: scoped_lock .
coerenza del programma: le specifiche C ++ indicano che non si dovrebbe mai generare un'eccezione prima che sia stata gestita un'eccezione esistente. Ciò significa che il processo di svolgimento dello stack non dovrebbe mai generare un'eccezione (utilizzare solo il codice garantito per non gettare nei distruttori o circondare tutto nei distruttori con try {
e } catch(...) {}
).
Se un distruttore genera un'eccezione durante lo svolgimento dello stack, si finisce nella terra di un comportamento indefinito che potrebbe causare la chiusura imprevista del programma (comportamento più comune) o la fine dell'universo (teoricamente possibile ma non ancora osservato nella pratica).
In senso generale, uno stack "svolgersi" è praticamente sinonimo di fine di una chiamata di funzione e del successivo pop-up dello stack.
Tuttavia, in particolare nel caso di C ++, lo svolgimento dello stack ha a che fare con il modo in cui C ++ chiama i distruttori per gli oggetti allocati dall'inizio di qualsiasi blocco di codice. Gli oggetti creati all'interno del blocco vengono deallocati in ordine inverso rispetto alla loro allocazione.
try
blocchi. Stack oggetti allocati in qualsiasi blocco (indipendentemente dal try
fatto) è soggetto a svolgersi o meno quando il blocco esce.
Lo svolgendo dello stack è un concetto prevalentemente C ++, che tratta di come gli oggetti allocati nello stack vengono distrutti quando si esce dal suo ambito (normalmente o tramite un'eccezione).
Supponi di avere questo frammento di codice:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Non so se hai ancora letto questo, ma l'articolo di Wikipedia sullo stack di chiamate ha una spiegazione decente.
di svolgimento:
Il ritorno dalla funzione chiamata farà apparire il frame superiore fuori dallo stack, forse lasciando un valore di ritorno. Viene chiamato l'atto più generale di estrarre uno o più frame dallo stack per riprendere l'esecuzione altrove nel programma stack svolgendo e deve essere eseguito quando si utilizzano strutture di controllo non locali, come quelle utilizzate per la gestione delle eccezioni. In questo caso, il frame dello stack di una funzione contiene una o più voci che specificano i gestori di eccezioni. Quando viene generata un'eccezione, lo stack viene srotolato fino a quando viene trovato un gestore pronto a gestire (catturare) il tipo di eccezione generata.
Alcune lingue hanno altre strutture di controllo che richiedono svolgimento generale. Pascal consente a un'istruzione goto globale di trasferire il controllo da una funzione nidificata a una funzione esterna precedentemente invocata. Questa operazione richiede che lo stack venga svolto, rimuovendo tutti i frame dello stack necessari per ripristinare il contesto appropriato per trasferire il controllo all'istruzione target all'interno della funzione esterna inclusa. Allo stesso modo, C ha le funzioni setjmp e longjmp che agiscono come gotos non locali. Common Lisp consente il controllo di ciò che accade quando lo stack viene svolto utilizzando l'operatore speciale di protezione da svolgimento.
Quando si applica una continuazione, lo stack viene (logicamente) srotolato e quindi riavvolto con lo stack della continuazione. Questo non è l'unico modo per implementare le continuazioni; ad esempio, usando più stack espliciti, l'applicazione di una continuazione può semplicemente attivare il suo stack e caricare un valore da trasmettere. Il linguaggio di programmazione Scheme consente l'esecuzione di thunk arbitrari in punti specifici su "svolgendo" o "riavvolgendo" la pila di controlli quando viene invocata una continuazione.
Ispezione [modifica]
Ho letto un post sul blog che mi ha aiutato a capire.
Che cos'è lo svolgersi dello stack?
In qualsiasi linguaggio che supporti funzioni ricorsive (cioè praticamente tutto tranne Fortran 77 e Brainf * ck) il runtime linguistico mantiene una pila di quali funzioni sono attualmente in esecuzione. Lo svolgimento della pila è un modo per ispezionare, e possibilmente modificare, quella pila.
Perché vorresti farlo?
La risposta può sembrare ovvia, ma ci sono diverse situazioni correlate, ma leggermente diverse, in cui lo svolgimento è utile o necessario:
- Come meccanismo di controllo del flusso di runtime (eccezioni C ++, C longjmp (), ecc.).
- In un debugger, per mostrare all'utente lo stack.
- In un profiler, per prendere un campione dello stack.
- Dal programma stesso (come da un gestore di crash per mostrare lo stack).
Questi hanno requisiti leggermente diversi. Alcuni di questi sono critici per le prestazioni, altri no. Alcuni richiedono la possibilità di ricostruire i registri dal frame esterno, altri no. Ma ci occuperemo di tutto ciò in un secondo.
Puoi trovare il post completo qui .
Tutti hanno parlato della gestione delle eccezioni in C ++. Ma penso che ci sia un'altra connotazione per lo svolgersi dello stack e che è correlata al debug. Un debugger deve eseguire lo svolgimento dello stack ogni volta che si suppone che debba passare a un frame precedente al frame corrente. Tuttavia, si tratta di una sorta di svolgimento virtuale in quanto deve riavvolgere quando torna al frame corrente. L'esempio per questo potrebbe essere i comandi su / giù / bt in gdb.
IMO, il diagramma di seguito riportato in questo articolo spiega meravigliosamente l'effetto dello svolgersi dello stack sul percorso dell'istruzione successiva (da eseguire una volta generata un'eccezione non rilevata):
Nella foto:
Nel secondo caso, quando si verifica un'eccezione, lo stack di chiamate di funzione viene cercato in modo lineare per il gestore di eccezioni. La ricerca termina con la funzione con il gestore delle eccezioni, cioè main()
con il try-catch
blocco racchiuso , ma non prima di rimuovere tutte le voci precedenti dallo stack delle chiamate di funzione.
Il runtime C ++ distrugge tutte le variabili automatiche create tra il lancio e il fermo. In questo semplice esempio sotto f1 () i tiri e le catture main (), tra gli oggetti di tipo B e A vengono creati nello stack in quell'ordine. Quando viene lanciato f1 (), vengono chiamati i distruttori di B e A.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
L'output di questo programma sarà
B's dtor
A's dtor
Questo perché il callstack del programma quando f1 () lancia sembra
f1()
f()
main()
Quindi, quando f1 () viene visualizzato, la variabile automatica b viene distrutta, e quindi quando f () viene visualizzato, la variabile automatica a viene distrutta.
Spero che questo aiuti, buona programmazione!
Quando viene generata un'eccezione e il controllo passa da un blocco try a un gestore, il runtime C ++ chiama i distruttori per tutti gli oggetti automatici costruiti dall'inizio del blocco try. Questo processo è chiamato svolgitura dello stack. Gli oggetti automatici vengono distrutti in ordine inverso rispetto alla loro costruzione. (Gli oggetti automatici sono oggetti locali che sono stati dichiarati auto o registrati o non dichiarati statici o esterni. Un oggetto automatico x viene eliminato ogni volta che il programma esce dal blocco in cui viene dichiarato x.)
Se viene generata un'eccezione durante la costruzione di un oggetto costituito da oggetti secondari o elementi dell'array, i distruttori vengono chiamati solo per quegli oggetti secondari o elementi dell'array costruiti correttamente prima che venga generata l'eccezione. Un distruttore per un oggetto statico locale verrà chiamato solo se l'oggetto è stato costruito correttamente.
Nello stack Java lo svolgersi o lo svolgersi non è molto importante (con Garbage Collector). In molti documenti sulla gestione delle eccezioni ho visto questo concetto (stack svolgendo), in particolare quegli scrittori si occupano della gestione delle eccezioni in C o C ++. con try catch
blocchi che non dovremmo dimenticare: stack gratuito da tutti gli oggetti dopo i blocchi locali .