Come può essere? La memoria di una variabile locale non è inaccessibile al di fuori della sua funzione?
Si affitta una stanza d'albergo. Metti un libro nel primo cassetto del comodino e vai a dormire. Fai il check out la mattina dopo, ma "dimentica" di restituire la chiave. Hai rubato la chiave!
Una settimana dopo, ritorni in hotel, non fare il check-in, sgattaiolare nella tua vecchia stanza con la chiave rubata e guardare nel cassetto. Il tuo libro è ancora lì. Stupefacente!
Come può essere? Il contenuto del cassetto di una stanza d'albergo non è inaccessibile se non l'hai affittata?
Bene, ovviamente quello scenario può accadere nel mondo reale senza problemi. Non esiste una forza misteriosa che fa scomparire il tuo libro quando non sei più autorizzato a trovarti nella stanza. Né esiste una forza misteriosa che ti impedisce di entrare in una stanza con una chiave rubata.
La direzione dell'hotel non è tenuta a rimuovere il tuo libro. Non hai stipulato un contratto con loro che dice che se lasci qualcosa alle loro spalle, te lo distruggeranno. Se rientri illegalmente nella tua camera con una chiave rubata per riaverla, il personale di sicurezza dell'hotel non è tenuto a sorprenderti di nascosto. Non hai stipulato un contratto con loro che dice "se provo a intrufolarmi di nuovo nel mio stanza più tardi, devi fermarmi. " Piuttosto, hai firmato un contratto con loro che diceva "Prometto di non rientrare più tardi nella mia stanza", un contratto che hai rotto .
In questa situazione può succedere di tutto . Il libro può essere lì - sei stato fortunato. Il libro di qualcun altro può essere lì e il tuo potrebbe essere nella fornace dell'hotel. Qualcuno potrebbe essere lì quando entri, facendo a pezzi il tuo libro. L'hotel avrebbe potuto rimuovere completamente il tavolo e prenotare e sostituirlo con un armadio. L'intero hotel potrebbe essere quasi distrutto e sostituito con uno stadio di calcio, e morirai in un'esplosione mentre ti stai avvicinando di soppiatto.
Non sai cosa succederà; quando hai fatto il check out dall'hotel e hai rubato una chiave da utilizzare illegalmente in seguito, hai rinunciato al diritto di vivere in un mondo prevedibile e sicuro perché hai scelto di infrangere le regole del sistema.
C ++ non è un linguaggio sicuro . Ti permetterà allegramente di infrangere le regole del sistema. Se provi a fare qualcosa di illegale e sciocco come tornare in una stanza in cui non sei autorizzato a entrare e frugare in una scrivania che potrebbe anche non esserci più, C ++ non ti fermerà. Linguaggi più sicuri del C ++ risolvono questo problema limitando il tuo potere, ad esempio con un controllo molto più rigoroso sui tasti.
AGGIORNARE
Santo cielo, questa risposta sta suscitando molta attenzione. (Non sono sicuro del perché - l'ho considerato solo una piccola "analogia" divertente, ma qualunque cosa.)
Ho pensato che potrebbe essere germano aggiornarlo un po 'con qualche pensiero tecnico in più.
I compilatori si occupano della generazione di codice che gestisce la memorizzazione dei dati manipolati da quel programma. Esistono molti modi diversi di generare codice per gestire la memoria, ma nel tempo si sono radicate due tecniche di base.
Il primo è avere una sorta di area di memoria "longevo" in cui la "durata" di ciascun byte nella memoria - ovvero il periodo di tempo in cui è validamente associato ad una variabile di programma - non può essere facilmente prevista in anticipo di tempo. Il compilatore genera chiamate in un "gestore di heap" che sa come allocare dinamicamente lo spazio di archiviazione quando è necessario e recuperarlo quando non è più necessario.
Il secondo metodo consiste nell'avere un'area di archiviazione "di breve durata" in cui è nota la durata di ciascun byte. Qui, le vite seguono uno schema di "nidificazione". La più lunga durata di queste variabili di breve durata verrà allocata prima di qualsiasi altra variabile di breve durata e verrà liberata per ultima. Le variabili a vita più breve verranno allocate dopo quelle a vita più lunga e verranno liberate prima di esse. La durata di queste variabili di breve durata è "nidificata" all'interno di quelle di più lunga durata.
Le variabili locali seguono quest'ultimo schema; quando viene inserito un metodo, le sue variabili locali prendono vita. Quando quel metodo chiama un altro metodo, le variabili locali del nuovo metodo prendono vita. Saranno morti prima che le variabili locali del primo metodo siano morte. L'ordine relativo degli inizi e dei finali delle vite degli archivi associati alle variabili locali può essere elaborato in anticipo.
Per questo motivo, le variabili locali vengono generalmente generate come memoria su una struttura di dati "stack", poiché uno stack ha la proprietà che la prima cosa che viene spinta su di essa sarà l'ultima cosa spuntata.
È come se l'hotel decidesse di affittare solo le camere in sequenza, e non è possibile effettuare il check-out fino a quando tutti con un numero di camera superiore a quello che si è verificato.
Quindi pensiamo allo stack. In molti sistemi operativi si ottiene uno stack per thread e lo stack viene assegnato a una determinata dimensione fissa. Quando chiami un metodo, le cose vengono inserite nello stack. Se poi passi un puntatore allo stack fuori dal tuo metodo, come fa il poster originale qui, è solo un puntatore al centro di un blocco di memoria da un milione di byte completamente valido. Nella nostra analogia, fai il check out dall'hotel; quando lo fai, sei appena uscito dalla stanza occupata con il numero più alto. Se nessun altro effettua il check-in dopo di te e torni nella tua camera illegalmente, tutte le tue cose sono garantite per essere ancora lì in questo particolare hotel .
Usiamo stack per negozi temporanei perché sono davvero economici e facili. Non è richiesta un'implementazione di C ++ per utilizzare uno stack per l'archiviazione dei locali; potrebbe usare l'heap. Non lo fa, perché ciò renderebbe il programma più lento.
Non è necessaria un'implementazione di C ++ per lasciare intatta la spazzatura che hai lasciato nello stack in modo da poter tornare in seguito in modo illegale; è perfettamente legale per il compilatore generare codice che ritorni a zero tutto nella "stanza" che hai appena lasciato libero. Non perché, di nuovo, sarebbe costoso.
Non è necessaria un'implementazione di C ++ per garantire che quando lo stack si restringe logicamente, gli indirizzi che erano validi erano ancora mappati in memoria. L'implementazione può dire al sistema operativo "abbiamo finito di usare questa pagina di stack ora. Fino a quando non dico diversamente, emettere un'eccezione che distrugge il processo se qualcuno tocca la pagina di stack precedentemente valida". Ancora una volta, le implementazioni non lo fanno perché è lenta e non necessaria.
Invece, le implementazioni ti consentono di fare errori e cavartela. La maggior parte delle volte. Fino a quando un giorno qualcosa di veramente terribile va storto e il processo esplode.
Questo è problematico. Ci sono molte regole ed è molto facile infrangerle accidentalmente. Ho sicuramente molte volte. E peggio ancora, il problema si presenta spesso solo quando la memoria viene rilevata come miliardi di nanosecondi corrotti dopo che si è verificata la corruzione, quando è molto difficile capire chi l'ha incasinata.
Linguaggi più sicuri per la memoria risolvono questo problema limitando la tua potenza. In C # "normale" semplicemente non c'è modo di prendere l'indirizzo di un locale e restituirlo o conservarlo per dopo. Puoi prendere l'indirizzo di un locale, ma la lingua è progettata in modo intelligente in modo che sia impossibile usarlo dopo la fine del ciclo di vita locale. Per prendere l'indirizzo di un locale e passarlo indietro, devi mettere il compilatore in una modalità speciale "non sicura", e mettere la parola "non sicuro" nel tuo programma, per richiamare l'attenzione sul fatto che probabilmente stai facendo qualcosa di pericoloso che potrebbe infrangere le regole.
Per ulteriori letture:
address of local variable ‘a’ returned
; spettacoli di ValgrindInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr