Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Dubbio sulla gestione degli errori con throw/catch
Forum - C/C++ - Dubbio sulla gestione degli errori con throw/catch

Pagine: [ 1 2 ] Precedente | Prossimo
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 17:05
Domenica, 23/08/2015
A chi mi legge, il solito cordialissimo "ciao".

Ho trovato un corso di C++ qui...

http://www.bo.cnr.it/corsi-di-informatica/corsoCstandard/L ...

...e siccome ritengo che leggere da una fonte diversa le cose che già si sanno permette di scoprire particolari persi, fraintendimenti, pensieri nascosti tra le righe e dubbi ai quali manco si era pensato sto scorrendone le pagine. Sono dunque giunto alla sezione dove si tratta la gesione degli errori e, inevitabilmente, viene tirato in ballo il meccanismo throw/catch. In particolare, c'è una frase che attiva nel mio cervellino il timore d'aver seguito fino ad ora una strada sbagliata in merito. Ecco la frase:

"In termini tecnici, la funzione che rileva l'errore 'solleva' o 'lancia' (throw) un'eccezione ('marcandola' in qualche modo, come vedremo) e termina: l'area stack è ripercorsa all'indietro e cancellata (stack unwinding) a vari livelli finchè il flusso del programma non raggiunge il punto (se esiste) in cui l'eccezione può essere riconosciuta e 'catturata' (catch)"

La perplessità nasce dal quel "l'area dello stack è ripercorsa all'indietro e cancellata"... perché "e cancellata"? Significa FORSE (non ci avevo mai pensato) che se in precedenza ho allocato della memoria dinamica il meccanismo provvede IN AUTOMATICO a liberarla senza alcun intervento da parte mia? Ovvero, volendo fare un esempio...

Codice sorgente - presumibilmente C++

  1. // il main() e' in fondo...
  2.  
  3. #include <stdio.h>
  4.  
  5. const char *PIPPOErr_AllocError = "allocation error";
  6.  
  7. class PIPPO { // Pippo rules!
  8.     public:
  9.         PIPPO();
  10.         PIPPO( unsigned long dim );
  11.         virtual ~PIPPO();
  12.  
  13.         void RememberThisTwice( const char *s );
  14.  
  15.     private:
  16.         char *s1;
  17.         char *s2;
  18.         unsigned long nChars;
  19. };
  20.  
  21. // costruttore di default, non parametrizzato
  22. PIPPO::PIPPO() {
  23.     try {
  24.         s1 = new char[64];
  25.         s2 = new char[64];
  26.         nChars = 64;
  27.     } catch( ... ) {
  28.         throw PIPPOErr_AllocError;
  29.     }
  30. }
  31.  
  32. // costruttore parametrizzato
  33. PIPPO::PIPPO( unsigned long dim ) {
  34.     try {
  35.         s1 = new char[64];
  36.         s2 = new char[dim];
  37.         nChars = dim;
  38.     } catch( ... ) {
  39.         throw PIPPOErr_AllocError;
  40.     }
  41. }
  42.  
  43. // distruttore
  44. PIPPO::~PIPPO() {
  45.     delete[] s1;
  46.     delete[] s2;
  47. }
  48.  
  49. // metodo senza senso, giusto come esempio
  50. void PIPPO::RememberThisTwice( const char *s ) {
  51.     const char *p;
  52.     char *p1, *p2;
  53.  
  54.     for( p=s, p1=s1, p2=s2; *p; ++p, ++p1, ++p2 ) {
  55.         *p1 = *p;
  56.         *p2 = *p;
  57.     }
  58.  
  59.     nChars = p-s;
  60. }
  61.  
  62. int main() {
  63.     try {
  64.         PIPPO pippo( 0xFFFFFFFF );
  65.         pippo.RememberThisTwice( "Ciao!" );
  66.     } catch( const char *PIPPO_Err ) {
  67.         printf( "\n\n ERROR! %s\n\n", PIPPO_Err );
  68.     }
  69.  
  70.     printf( "\n Just press \"Enter\" to quit...    " );
  71.     getchar();
  72.     return 0;
  73. }



In questo programma senza senso, sul mio PC con due soli gigabyte di RAM, alla creazione dell'oggetto "pippo" il costruttore alloca dinamicamente senza problemi s1[64], ma ha qualche "problemino" ad allocare s2[0xFFFFFFFF]. Quando l'allocazione fallisce, new lancia un'eccezione bloccando l'esecuzione del costruttore, ma ormai un po' di memoria dinamica è già stata allocata e dovrebbe essere liberata. L'esecuzione viene quindi "rimandata" al blocco catch del costruttore dove il messaggio d'errore standard viene convertito in PIPPOErr_AllocError e "rimbalzato" nel main per l'ulteriore eventuale elaborazione.

Fino ad oggi, per evitare memory leaks, il mio modo di procedere è sempre consistito nel provvedere a un costruttore che liberasse "attivamente" la memoria già allocata prima di "rimbalzare" l'errore, tipo...

Codice sorgente - presumibilmente C++

  1. PIPPO::PIPPO( unsigned long dim ) {
  2.     try {
  3.         s1 = s2 = NULL;
  4.         s1 = new char[64];
  5.         s2 = new char[dim];
  6.         nChars = dim;
  7.     } catch( ... ) {
  8.         delete[] s1;
  9.         throw PIPPOErr_AllocError;
  10.     }
  11. }



... ma la frase che ho riportato, come ho detto, pare lasciar intendere che la memoria allocata venga liberata in automatico (benché gli "esperimenti" che ho fatto sembrino dimostrare il contrario).

Se c'è una cosa che detesto è non avere le idee ben chiare su questo genere di cose, perché poi pianto su dei pasticci senza né capo né coda e mi demotivo di brutto. Chi mi aiuta?


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 17:28
Domenica, 23/08/2015
Quella frase si riferisce al call stack. E new prima di allocare controlla che ci sia lo spazio, perché new alloca un solo blocco contiguo di memoria, quindi non lascia pezzi allocati di memoria: o alloca tutto o niente. Puoi vedere un throw come una forzatura a continui return fino al ritrovamento del catch. Capito?

PM Quote
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 17:35
Domenica, 23/08/2015
Intanto sono colpito dalla tua velocità! :)

Sì, ho capito la questione dei return "a catena", ma nella situazione che ho esemplificato, new è riuscito ad allocare con successo la porzione di memoria richiesta con la prima chiamata, fallendo nell'allocare la seconda. Dunque, quando "scatta" il return forzato, il primo pezzetto di memoria allocato ha due possibili destini: o rimane lì (ed è quello che ho pensato finora) e va quindi deallocato "manualmente" con delete, o viene deallocato automaticamente da qualche meccanismo intrinseco. Forse la risposta è nascosta nel fatto che le funzioni vengono allocate nello stack mentre new alloca da un'altra parte (nell'heap?)), per cui "cancellare lo stack" non significherebbe liberare la memoria allocata dinamicamente da new, ma solo quella allocata per fare spazio alle funzioni e alle loro variabili e parametri non dinamici. Ci ho preso?

Scusa se parlo come mangio, ma sono un autodidatta e questo ha le sue conseguenze.


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 17:53
Domenica, 23/08/2015
New è diverso da malloc per due ragioni : non è detto che new allochi nell heap E new chiama il costruttore.  Scusa se ho frainteso la domanda, la risposta è : la prima memoria allocata rimane allocata. Se non liberi la memoria rimane allocata. Non so se alla terminazione del  programma la runtime del so elimini la memoria, ma questo non è un comportamento standard. Quindi a lanciare eccezioni tra new (riuscito)  e delete si incorre in memory leak.

PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 17:55
Domenica, 23/08/2015
P. S.  Non vedo che parli come mangi..... Comunque anche io sono un autodidatta.

PM Quote
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 19:05
Domenica, 23/08/2015
TheDarkJuster: "[...] la prima memoria allocata rimane allocata

Allora era giusto il metodo che seguivo prima: intercettare l'eccezione con catch nell'ambito del costruttore e "far fuori" la memoria già allocata prima di "rimbalzare" l'eccezione con un nuovo throw. Continuerò a far così. Peccato perché sarebbe stato molto più lineare nel caso opposto... forse è stato il treno dei desideri a indurmi a voler credere che la frase di quel manuale sottintendesse un qualche automatismo magico! :)

Grazie per avermi aiutato a sciogliere il dubbio.

Ah, per quel che ne so (e non è certo una garanzia!) in Windows quando un processo viene terminato tutto quello che c'è di dinamico che compete a quel processo viene distrutto senza pietà -- file aperti, finestre aperte, oggetti GDI, memoria allocata... tutto. Sai cosa, pensavo che potrei scrivere un sistema operativo che gestisse in automatico ancora più di quello che fa Windows, cosa ne dici? Vuoi partecipare? :rotfl:


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 19:27
Domenica, 23/08/2015
Vedo molta confusione in questa discussione...

new/malloc allocano spazio sull'heap.

Variabili locali dichiarate ad esempio come:

Codice sorgente - presumibilmente C/C++

  1. int a;



Sono invece allocate sullo stack.

Quando viene scatenata un'eccezione, lo stack viene "cancellato", ma gli spazi allocati con new/malloc devono essere manualmente liberati (con delete/free).

Ultima modifica effettuata da pierotofy il 23/08/2015 alle 19:29


Il mio blog: https://piero.dev
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 20:20
Domenica, 23/08/2015
Da quel che so new non assicura l'allocazione nell'heap, perché è il so a decidere.

PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 21:36
Domenica, 23/08/2015
New può fallire nel caso non ci sia sufficiente memoria disponibile. Ma se la chiamata ha successo, lo spazio non verrà mai allocato sullo stack.

Non so dove hai letto che è il SO a decidere, ma sarei curioso di saperne di più se hai una fonte.

Ultima modifica effettuata da pierotofy il 23/08/2015 alle 21:42


Il mio blog: https://piero.dev
PM Quote
Pagine: [ 1 2 ] Precedente | Prossimo