2010-02-25 12 views
153

Che cos'è lo srotolamento dello stack? Ho cercato ma non ho trovato risposta illuminante!Che cos'è lo srotolamento dello stack?

+72

Se non sa cosa sia, come puoi aspettarti che sappia che non sono gli stessi per C e per C++? – dreamlax

+0

@dreamlax: Quindi, come il concetto di "stack unwinding" è diverso in C & C++? – Destructor

+1

@PravasiMeet: C non ha alcuna gestione di eccezioni, quindi lo stack unwinding è molto diretto, tuttavia, in C++, se viene lanciata un'eccezione o una funzione viene chiusa, lo stack unwinding comporta la distruzione di qualsiasi oggetto C++ con durata di archiviazione automatica. – dreamlax

risposta

115

Di solito viene descritto lo sbobinamento dello stack 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; 
} 

Qui memoria allocata per pleak andranno persi se viene lanciata un'eccezione, mentre la memoria allocata per s sarà opportunamente distribuito dalla std::string distruttore, in ogni caso. Gli oggetti allocati nello stack vengono "srotolati" quando si esce dall'ambito (qui lo scopo è della funzione func.) 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, cioè Resource acquisizione è di inizializzazione, che ci aiuta a gestire le risorse come la memoria, le connessioni al database, descrittori di file aperti, ecc in C++.

Ora che ci consente di fornire exception safety guarantees.

+0

Questo è stato davvero illuminante! Quindi ottengo questo: se il mio processo si è arrestato inaspettatamente durante l'uscita da QUALSIASI blocco in cui è stato eseguito lo snap dello stack, potrebbe accadere che il codice dopo il codice gestore eccezioni non venga eseguito affatto e potrebbe causare perdite di memoria , corruzione dell'heap, ecc. –

+0

Hmm, crash è un po 'diverso, anche se correlato, animale (segnali, core dump, ecc.) Di cosa sto parlando sono gli strumenti/strutture C++ per gestire in sicurezza le risorse in presenza di eccezioni/errori , che * potrebbe * portare a un incidente se non gestito. –

+11

Se il programma "si blocca" (ad esempio * termina * a causa di un errore), qualsiasi perdita di memoria o danneggiamento dell'heap è irrilevante poiché la memoria viene rilasciata al termine. –

9

Non so se l'avete già letto, ma Wikipedia's article on the call stack ha una spiegazione decente.

Svolgitore:

ritorno dalla funzione chiamata apparirà il telaio superiore dallo stack, forse lasciando un valore di ritorno. L'azione più generica di uno o più frame fuori dallo stack per riprendere l'esecuzione altrove nel programma è denominata stack che si svolge e deve essere eseguita quando vengono utilizzate 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 lanciata un'eccezione, la pila viene svolta finché non viene trovato un gestore pronto a gestire (catturare) il tipo dell'eccezione generata.

Alcune lingue hanno altre strutture di controllo che richiedono lo srotolamento generale. Pascal consente a un'istruzione goto globale di trasferire il controllo da una funzione nidificata e in una funzione esterna precedentemente richiamata. Questa operazione richiede che lo stack venga svolto, rimuovendo tutti i frame di stack necessari per ripristinare il contesto appropriato per trasferire il controllo all'istruzione di destinazione all'interno della funzione esterna che lo racchiude. Allo stesso modo, C ha le funzioni setjmp e longjmp che agiscono come goto non locali. Common Lisp consente il controllo di ciò che accade quando lo stack viene svolto utilizzando l'operatore speciale unwind-protect.

Quando si applica una continuazione, lo stack viene (logicamente) svolto e quindi riavvolto con lo stack della continuazione. Questo non è l'unico modo per implementare le continuazioni; per esempio, usando stack multipli espliciti, l'applicazione di una continuazione può semplicemente attivare il suo stack e caricare un valore da passare. Il linguaggio di programmazione Scheme consente di eseguire arbitrari thunk in punti specificati su "unwinding" o "rewinding" dello stack di controllo quando viene invocata una continuazione.

ispezione [modifica]

12

Stack svolgitura è un concetto prevalentemente C++, trattare come gli oggetti pila allocati vengono distrutte quando il campo di applicazione si esce (sia normalmente o tramite un'eccezione).

Diciamo che avete 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" 
+0

Questo vale per qualsiasi blocco? Voglio dire se c'è solo { // alcuni oggetti locali } –

+0

@Rajendra: Sì, un blocco anonimo definisce un'area di scopo, quindi conta anche. –

38

In senso generale, una pila "staccare la spina" è praticamente sinonimo del termine di una chiamata di funzione e il conseguente scoppio della pila.

Tuttavia, in particolare nel caso di C++, lo sbobinamento 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 che sono stati creati all'interno del blocco vengono deallocati in ordine inverso rispetto alla loro allocazione.

+4

Non c'è niente di speciale nei blocchi 'try'. Gli oggetti stack allocati in _any_ block (che si tratti di 'try' o no) sono soggetti a svolgimento quando il blocco viene chiuso. –

+0

Oh, mio ​​male. Qualsiasi blocco : P – jrista

+0

È passato un po 'di tempo da quando ho fatto molto codice C++. Dovevo scavare quella risposta fuori dalle profondità arrugginite. ; P – jrista

48

Tutto questo si riferisce a C++:

Definizione: Come si creano oggetti staticamente (in pila in contrasto con le assegnano in memoria heap) ed effettuare chiamate di funzione, sono "accatastati".

Quando un ambito (nulla delimitato da { e }) si esce (utilizzando return XXX;, raggiungendo la fine dell'ambito o un'eccezione) tutto in tale ambito viene distrutta (distruttori sono chiamati per tutto). Questo processo di distruggere oggetti locali e chiamare i distruttori è chiamato stack unwinding.

sono disponibili le seguenti questioni relative alla pila di svolgimento:

  1. perdite di memoria evitando (niente dinamicamente assegnata che non è gestita da un oggetto locale e ripulito nel distruttore sarà trapelato) - vedi Raii referred to di Nikolai e the documentation for boost::scoped_ptr o questo esempio di utilizzo di boost::mutex::scoped_lock.

  2. consistenza del programma: le specifiche C++ dichiarano che non si dovrebbe mai generare un'eccezione prima di gestire un'eccezione esistente. Ciò significa che il processo di svolgimento dello stack non deve mai generare un'eccezione (utilizzare solo il codice garantito per non gettare i distruttori o circondare tutto in distruttori con try { e } catch(...) {}).

Se un distruttore genera un'eccezione durante pila di svolgimento si finisce nel terra di un comportamento indefinito che potrebbe causare il vostro programma per treminate inaspettatamente (comportamento più comune) o l'universo alla fine (teoricamente possibile, ma ha non è stato ancora osservato nella pratica).

+2

Al contrario. Mentre non si dovrebbe abusare di gotos, essi causano lo srotolamento dello stack in MSVC (non in GCC, quindi è probabilmente un'estensione). setjmp e longjmp lo fanno in modo multipiattaforma, con un po 'meno flessibilità. –

+10

Ho appena provato questo con gcc e chiama correttamente i distruttori quando si esce da un blocco di codice. Vedi http://stackoverflow.com/questions/334780/are-goto-and-destructors-compatible/334781#334781 - come menzionato in questo link, anche questo fa parte dello standard. – Damyan

+1

leggendo Nikolai's, jrista's e la tua risposta in questo ordine, ora ha senso! – n611x007

1

Quando viene generata un'eccezione e il controllo passa da un blocco try a un gestore, il tempo di esecuzione C++ chiama i distruttori per tutti gli oggetti automatici costruiti dall'inizio del blocco try. Questo processo è chiamato stack unwinding. Gli oggetti automatici vengono distrutti in ordine inverso rispetto alla loro costruzione. Gli oggetti automatici sono oggetti locali dichiarati auto o registrati o non dichiarati statici o extern Un oggetto automatico x viene eliminato ogni volta che il programma esce dal blocco in cui viene dichiarata x.

Se viene generata un'eccezione durante costruzione di un oggetto costituito da oggetti subobjects o array, i distruttori vengono chiamati solo per quei sottooggetti o elementi dell'array costruiti con successo prima che l'eccezione venisse lanciata. Un distruttore per un oggetto statico locale verrà chiamato solo se l'oggetto è stato costruito correttamente.

6

Tutti hanno parlato della gestione delle eccezioni in C++.Ma, penso che ci sia un'altra connotazione per lo srotolamento dello stack e che è legato al debugging. Un debugger deve eseguire lo sbobinamento dello stack ogni volta che deve andare in un frame precedente al frame corrente. Tuttavia, questa è una sorta di svolgimento virtuale in quanto ha bisogno di riavvolgere quando ritorna al frame corrente. L'esempio per questo potrebbe essere i comandi su/giù/bt in gdb.

+5

L'azione di debugger viene in genere denominata "Stack Walking" che sta semplicemente analizzando lo stack. "Stack Unwinding" implica non solo "Stack Walking" ma anche il richiamo dei distruttori di oggetti esistenti nello stack. – Adisak

+0

@Adisak Non sapevo che si chiamasse anche "stack walking". Ho sempre visto "stack unwinding" nel contesto di tutti gli articoli debugger e anche all'interno del codice gdb. Ho ritenuto che lo "stack unwinding" fosse più appropriato in quanto non si tratta semplicemente di dare una occhiata alle informazioni di stack per ogni funzione, ma implica lo srotolamento delle informazioni di frame (c.f. CFI in nano). Questo viene elaborato in ordine di una funzione di uno. – bbv

+0

Immagino che lo "stack walking" sia reso più famoso da Windows. Inoltre, ho trovato come esempio https://code.google.com/p/google-breakpad/wiki/StackWalking a parte lo stesso documento dello standard dwarf che utilizza il termine sbozzare alcune volte. Sebbene sia d'accordo, è lo srotolamento virtuale. Inoltre, la domanda sembra chiedere ogni possibile significato che lo "stack unwinding" può suggerire. – bbv

1

Nella pila Java unwiding o unwir non è molto importante (con garbage collector). In molti documenti di gestione delle eccezioni ho visto questo concetto (stack unwinding), in particolare quei writter si occupano della gestione delle eccezioni in C o C++. con blocchi try catch non dimentichiamo: stack libero da tutti gli oggetti dopo i blocchi locali.

2

Il runtime di C++ distrugge tutte le variabili automatiche create tra il lancio di presa &. In questo semplice esempio, sotto gli attacchi f1() throws e main(), tra gli oggetti di tipo B e A vengono creati nello stack in quell'ordine. Quando f1() lancia, 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é stack del programma quando f1() throws assomiglia

f1() 
f() 
main() 

Così, quando è spuntato f1(), la variabile automatica b viene distrutta e quindi quando f() viene spuntato, la variabile automatica a viene distrutta.

Spero che questo aiuti, felice codifica!

8

Ho letto un post sul blog che mi ha aiutato a capire.

Che cos'è lo srotolamento dello stack?

In qualsiasi linguaggio che supporta le funzioni ricorsive (es. Più o meno tutto tranne Fortran 77 e Brainf * ck) language runtime mantiene una pila di quali funzioni sono attualmente in esecuzione. Pila di svolgimento è un modo di controllo, ed eventualmente modificare, quella pila.

Perché vorresti farlo?

La risposta può sembrare ovvio, ma ci sono alcune connesse, eppure sottilmente diverse, situazioni in cui svolgimento è utile o necessario:

  1. Come un meccanismo di controllo del flusso di esecuzione (eccezioni C++, C longjmp (), eccetera).
  2. In un debugger, per mostrare all'utente la pila.
  3. In un profiler, per prelevare un campione della pila.
  4. 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 l'abilità per ricostruire i registri dal frame esterno, altri no. Ma entreremo in tutto questo in un secondo.

È possibile trovare il post completo here.

1

IMO, il dato sotto diagramma in questo article ben spiega l'effetto della pila svolgimento sul percorso della successiva istruzione (da eseguire una volta viene generata un'eccezione che viene catturata):

enter image description here

In l'immagine:

  • Il primo è un'esecuzione di chiamata normale (senza eccezione generata).
  • Quello inferiore quando viene generata un'eccezione.

Nel secondo caso, quando si verifica un'eccezione, lo stack di chiamata di funzione viene ricercato linearmente per il gestore di eccezioni. La ricerca termina con la funzione con gestore di eccezioni, ovvero main() con blocco try-catch, ma non prima di rimuovendo tutte le voci precedenti dallo stack di chiamata di funzione.

Problemi correlati